summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/actions/make-snapshot/action.yml6
-rw-r--r--.github/workflows/tarball-macos.yml70
-rw-r--r--.github/workflows/tarball-test-schedule.yml2
-rw-r--r--.github/workflows/tarball-test.yml13
-rw-r--r--.github/workflows/tarball-ubuntu.yml174
-rw-r--r--.github/workflows/tarball-windows.yml38
-rw-r--r--bootstraptest/test_ractor.rb21
-rw-r--r--common.mk4
-rw-r--r--ext/strscan/strscan.c8
-rw-r--r--gc.c28
-rw-r--r--gc/default/default.c275
-rw-r--r--gc/gc.h30
-rw-r--r--imemo.c4
-rw-r--r--pathname_builtin.rb126
-rw-r--r--ruby_atomic.h40
-rw-r--r--template/configure-ext.mk.tmpl27
-rw-r--r--test/fiber/test_thread.rb6
-rw-r--r--test/ruby/test_defined.rb28
-rw-r--r--vm_callinfo.h2
-rw-r--r--vm_insnhelper.c10
-rw-r--r--vm_method.c2
-rw-r--r--yjit/src/cruby_bindings.inc.rs3
-rw-r--r--zjit/src/cruby_bindings.inc.rs3
-rw-r--r--zjit/src/hir.rs41
-rw-r--r--zjit/src/profile.rs15
25 files changed, 616 insertions, 360 deletions
diff --git a/.github/actions/make-snapshot/action.yml b/.github/actions/make-snapshot/action.yml
index f8d26dd989..4552f0e067 100644
--- a/.github/actions/make-snapshot/action.yml
+++ b/.github/actions/make-snapshot/action.yml
@@ -18,6 +18,10 @@ inputs:
description: 'srcdir for tool/make-snapshot. Empty = clone ruby/ruby into ./ruby.'
required: false
default: ''
+ upload-artifact:
+ description: 'Upload Packages and Info as workflow artifacts. Pass "false" when callers run in a matrix that would collide on artifact names.'
+ required: false
+ default: 'true'
runs:
using: "composite"
@@ -65,7 +69,9 @@ runs:
with:
name: Packages
path: pkg
+ if: ${{ inputs.upload-artifact == 'true' }}
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Info
path: pkg/info
+ if: ${{ inputs.upload-artifact == 'true' }}
diff --git a/.github/workflows/tarball-macos.yml b/.github/workflows/tarball-macos.yml
index f0bb22f0c0..e1d4d4ba2a 100644
--- a/.github/workflows/tarball-macos.yml
+++ b/.github/workflows/tarball-macos.yml
@@ -7,18 +7,8 @@ on:
description: 'archname (e.g. snapshot-master, snapshot-ruby_3_3)'
required: true
type: string
- allow-failures:
- description: 'TEST_BUNDLED_GEMS_ALLOW_FAILURES value'
- required: false
- type: string
- default: ''
- patch-url:
- description: 'Patch URL forwarded from workflow_dispatch'
- required: false
- type: string
- default: ''
- rebuild-homebrew-ruby:
- description: 'Uninstall and reinstall Homebrew Ruby on macos-15-intel'
+ notify-release-channel:
+ description: 'Also send failure notifications to SNAPSHOT_SLACK_WEBHOOK_URL (schedule/release builds).'
required: false
type: boolean
default: false
@@ -35,7 +25,7 @@ jobs:
fail-fast: false
runs-on: ${{ matrix.os }}
env:
- TEST_BUNDLED_GEMS_ALLOW_FAILURES: ${{ inputs.allow-failures }}
+ ARCHNAME: ${{ inputs.archname }}
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
@@ -43,16 +33,6 @@ jobs:
path: pkg
- name: Extract
run: tar xf pkg/*.tar.xz
- - name: Apply patch
- run: |
- set -x
- curl -sSL "${RUBY_PATCH_URL}" -o ruby.patch
- cd "${{ inputs.archname }}/"
- git apply ../ruby.patch
- shell: bash
- env:
- RUBY_PATCH_URL: ${{ inputs.patch-url }}
- if: inputs.patch-url != ''
- name: Install libraries
run: |
with_retry () {
@@ -60,45 +40,51 @@ jobs:
}
set -x
with_retry brew install gmp libffi openssl@1.1 zlib autoconf automake libtool readline libyaml
- - name: Uninstall Homebrew Ruby
- run: |
- for formula in $(brew list 2>/dev/null | grep '^ruby'); do
- brew uninstall --force "$formula"
- done
- sudo rm -rf /usr/local/lib/ruby
- if: inputs.rebuild-homebrew-ruby && matrix.os == 'macos-15-intel'
- name: Set ENV
run: |
echo "JOBS=-j$((1 + $(sysctl -n hw.activecpu)))" >> $GITHUB_ENV
- name: configure
- run: cd "${{ inputs.archname }}/" && ./configure --with-openssl-dir=$(brew --prefix openssl@1.1) --with-readline-dir=$(brew --prefix readline) --with-libyaml-dir=$(brew --prefix libyaml)
+ run: cd "$ARCHNAME/" && ./configure --with-openssl-dir=$(brew --prefix openssl@1.1) --with-readline-dir=$(brew --prefix readline) --with-libyaml-dir=$(brew --prefix libyaml)
- name: make
- run: cd "${{ inputs.archname }}/" && make $JOBS
- - name: Reinstall Homebrew Ruby
- run: brew install ruby
- if: inputs.rebuild-homebrew-ruby && matrix.os == 'macos-15-intel' && (matrix.test_task == 'test-bundled-gems' || matrix.test_task == 'test-bundler-parallel')
+ run: cd "$ARCHNAME/" && make $JOBS
- name: Tests
- run: cd "${{ inputs.archname }}/" && make $JOBS -s ${{ matrix.test_task }}
+ run: cd "$ARCHNAME/" && make $JOBS -s ${{ matrix.test_task }}
env:
RUBY_TESTOPTS: "-q --tty=no"
RUBY_DEBUG_TEST_NO_REMOTE: "1"
# leaked-globals since 2.7
- name: Leaked Globals
- run: cd "${{ inputs.archname }}/" && make -s leaked-globals
+ run: cd "$ARCHNAME/" && make -s leaked-globals
if: matrix.test_task == 'check'
- name: make install without root privilege
- run: cd "${{ inputs.archname }}/" && make $JOBS install DESTDIR="/tmp/destdir"
+ run: cd "$ARCHNAME/" && make $JOBS install DESTDIR="/tmp/destdir"
if: matrix.test_task == 'check'
- name: make install
- run: cd "${{ inputs.archname }}/" && sudo make $JOBS install
+ run: cd "$ARCHNAME/" && sudo make $JOBS install
if: matrix.test_task == 'check'
- - name: ruby -v
- run: /usr/local/bin/ruby -v
+ - name: Verify installed binaries
+ run: |
+ /usr/local/bin/ruby -v
+ /usr/local/bin/gem -v
+ /usr/local/bin/bundle -v
if: matrix.test_task == 'check'
- uses: ruby/action-slack@54175162371f1f7c8eb94d7c8644ee2479fcd375 # v3.2.2
with:
payload: |
{
+ "ci": "GitHub Actions",
+ "env": "snapshot: ${{ matrix.os }} / ${{ matrix.test_task }}",
+ "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}",
+ "commit": "${{ github.sha }}",
+ "branch": "${{ github.ref_name }}"
+ }
+ env:
+ SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }}
+ if: failure()
+ - uses: ruby/action-slack@54175162371f1f7c8eb94d7c8644ee2479fcd375 # v3.2.2
+ with:
+ payload: |
+ {
"attachments": [{
"text": "${{ job.status }}: ${{ matrix.os }} / ${{ matrix.test_task }} <https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|${{ github.workflow }}>",
"color": "danger"
@@ -106,4 +92,4 @@ jobs:
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SNAPSHOT_SLACK_WEBHOOK_URL }}
- if: failure() && github.event_name == 'schedule'
+ if: failure() && inputs.notify-release-channel
diff --git a/.github/workflows/tarball-test-schedule.yml b/.github/workflows/tarball-test-schedule.yml
index 225aeed633..abf80edf81 100644
--- a/.github/workflows/tarball-test-schedule.yml
+++ b/.github/workflows/tarball-test-schedule.yml
@@ -19,7 +19,7 @@ jobs:
- ruby_3_3
steps:
- name: Trigger tarball-test on ${{ matrix.branch }}
- run: gh workflow run tarball-test.yml --ref "$BRANCH" --repo "$GITHUB_REPOSITORY"
+ run: gh workflow run tarball-test.yml --ref "$BRANCH" --repo "$GITHUB_REPOSITORY" -f notify-release-channel=true
env:
BRANCH: ${{ matrix.branch }}
GH_TOKEN: ${{ secrets.MATZBOT_GITHUB_ACTION_TOKEN }}
diff --git a/.github/workflows/tarball-test.yml b/.github/workflows/tarball-test.yml
index 5b06b466fe..db49977cbf 100644
--- a/.github/workflows/tarball-test.yml
+++ b/.github/workflows/tarball-test.yml
@@ -13,6 +13,11 @@ on:
# https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks
merge_group:
workflow_dispatch:
+ inputs:
+ notify-release-channel:
+ description: 'Also send failure notifications to SNAPSHOT_SLACK_WEBHOOK_URL (set by tarball-test-schedule).'
+ type: boolean
+ default: false
concurrency:
group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }}
@@ -53,10 +58,7 @@ jobs:
uses: ./.github/workflows/tarball-ubuntu.yml
with:
archname: snapshot-${{ needs.tarball.outputs.branch }}
- allow-failures: power_assert
- remove-gnupg: true
- notify-ruby-sha: true
- branch-label: ${{ needs.tarball.outputs.branch }}
+ notify-release-channel: ${{ github.event_name == 'workflow_dispatch' && inputs.notify-release-channel || false }}
secrets: inherit
macos:
@@ -64,7 +66,7 @@ jobs:
uses: ./.github/workflows/tarball-macos.yml
with:
archname: snapshot-${{ needs.tarball.outputs.branch }}
- allow-failures: power_assert
+ notify-release-channel: ${{ github.event_name == 'workflow_dispatch' && inputs.notify-release-channel || false }}
secrets: inherit
windows:
@@ -72,6 +74,7 @@ jobs:
uses: ./.github/workflows/tarball-windows.yml
with:
archname: snapshot-${{ needs.tarball.outputs.branch }}
+ notify-release-channel: ${{ github.event_name == 'workflow_dispatch' && inputs.notify-release-channel || false }}
secrets: inherit
non_development:
diff --git a/.github/workflows/tarball-ubuntu.yml b/.github/workflows/tarball-ubuntu.yml
index d1c7a9bb78..4e4b2cf6d0 100644
--- a/.github/workflows/tarball-ubuntu.yml
+++ b/.github/workflows/tarball-ubuntu.yml
@@ -7,51 +7,11 @@ on:
description: 'archname (e.g. snapshot-master, snapshot-ruby_3_3)'
required: true
type: string
- allow-failures:
- description: 'TEST_BUNDLED_GEMS_ALLOW_FAILURES value'
- required: false
- type: string
- default: ''
- patch-url:
- description: 'Patch URL forwarded from workflow_dispatch'
- required: false
- type: string
- default: ''
- apt-mode:
- description: '"none" | "git-only" | "ruby-and-git" — controls per-test_task apt install handling'
- required: false
- type: string
- default: 'none'
- setup-host-ruby:
- description: 'Install Ruby 3.2 via ruby/setup-ruby for test-bundled-gems'
- required: false
- type: boolean
- default: true
- remove-gnupg:
- description: 'Forcibly remove ~/.gnupg after tests'
+ notify-release-channel:
+ description: 'Also send failure notifications to SNAPSHOT_SLACK_WEBHOOK_URL (schedule/release builds).'
required: false
type: boolean
default: false
- fixed-dirs-extra:
- description: 'Create $HOME/.local/share, $HOME/.ssh on top of minimal fixed dirs'
- required: false
- type: boolean
- default: true
- ruby-bin:
- description: 'Path to ruby for post-install version check'
- required: false
- type: string
- default: '/usr/local/bin/ruby'
- notify-ruby-sha:
- description: 'Post ruby SHA failure notification to SIMPLER_ALERTS_URL'
- required: false
- type: boolean
- default: false
- branch-label:
- description: 'Branch label for SIMPLER_ALERTS_URL notification. Defaults to "master".'
- required: false
- type: string
- default: 'master'
jobs:
ubuntu:
@@ -62,7 +22,7 @@ jobs:
fail-fast: false
runs-on: ${{ matrix.os }}
env:
- TEST_BUNDLED_GEMS_ALLOW_FAILURES: ${{ inputs.allow-failures }}
+ ARCHNAME: ${{ inputs.archname }}
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
@@ -70,97 +30,39 @@ jobs:
path: pkg
- name: Extract
run: tar xf pkg/*.tar.xz
- - name: Apply patch
- run: |
- set -x
- curl -sSL "${RUBY_PATCH_URL}" -o ruby.patch
- cd "${{ inputs.archname }}/"
- git apply ../ruby.patch
- shell: bash
- env:
- RUBY_PATCH_URL: ${{ inputs.patch-url }}
- if: inputs.patch-url != ''
- - name: Install libraries (apt-mode=none)
+ - name: Install libraries
run: |
set -x
sudo apt-get update -q
sudo apt-get install --no-install-recommends -q -y build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev bison- autoconf-
- if: inputs.apt-mode == 'none'
- - name: Install libraries (apt-mode=git-only)
- run: |
- set -x
- sudo apt-get update -q || :
- # postfix `-` means `uninstall`
- APT_INSTALL_GIT=git-
- case "${{ matrix.test_task }}" in
- test-bundled-gems)
- # test-bundled-gems-fetch requires git
- unset APT_INSTALL_GIT
- ;;
- test-bundler*)
- # avoid Bundler::Source::Git::GitNotInstalledError
- unset APT_INSTALL_GIT
- ;;
- *)
- ;;
- esac
- sudo apt-get install --no-install-recommends -q -y build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev bison- autoconf- $APT_INSTALL_GIT
- if: inputs.apt-mode == 'git-only'
- - name: Install libraries (apt-mode=ruby-and-git)
- run: |
- set -x
- sudo apt-get update -q || :
- # postfix `-` means `uninstall`
- APT_INSTALL_RUBY=ruby-
- APT_INSTALL_GIT=git-
- case "${{ matrix.test_task }}" in
- test-bundled-gems)
- # test-bundled-gems requires executable host ruby
- APT_INSTALL_RUBY=ruby
- # test-bundled-gems-fetch requires git
- unset APT_INSTALL_GIT
- ;;
- test-bundler*)
- # avoid Bundler::Source::Git::GitNotInstalledError
- unset APT_INSTALL_GIT
- ;;
- *)
- ;;
- esac
- sudo apt-get install --no-install-recommends -q -y build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev bison- autoconf- $APT_INSTALL_RUBY $APT_INSTALL_GIT
- if: inputs.apt-mode == 'ruby-and-git'
- uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
with:
ruby-version: 3.2
# test-bundled-gems requires executable host ruby
- if: inputs.setup-host-ruby && matrix.test_task == 'test-bundled-gems'
+ if: matrix.test_task == 'test-bundled-gems'
- name: Fixed world writable dirs
run: |
- mkdir -p $HOME/.local
+ mkdir -p $HOME/.local/share
mkdir -p $HOME/.cache/gem/specs
mkdir -p $HOME/.bundle/cache
+ mkdir -p $HOME/.ssh
chmod a-w $HOME/.bundle
# chmod a-w $HOME
# allow to write $HOME and check stats of HOME around tests (see below)
chmod -v a-w $HOME/.config
sudo chmod -R a-w /usr/share
sudo bash -c 'IFS=:; for d in '"$PATH"'; do chmod -v a-w $d; done' || :
- - name: Fixed world writable dirs (extra)
- run: |
- mkdir -p $HOME/.local/share
- mkdir -p $HOME/.ssh
- if: inputs.fixed-dirs-extra
- name: Set ENV
run: |
echo "JOBS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV
- name: configure
- run: cd "${{ inputs.archname }}/" && ./configure
+ run: cd "$ARCHNAME/" && ./configure
- name: make
- run: cd "${{ inputs.archname }}/" && make $JOBS
+ run: cd "$ARCHNAME/" && make $JOBS
- name: Save stats of HOME
run: |
set -euxo pipefail
- cd "${{ inputs.archname }}/"
+ cd "$ARCHNAME/"
cat >test.rb <<'EOF'
require 'pathname'
require 'digest'
@@ -187,18 +89,17 @@ jobs:
make runruby
rm -f test.rb
- name: Tests
- run: cd "${{ inputs.archname }}/" && make $JOBS -s ${{ matrix.test_task }}
+ run: cd "$ARCHNAME/" && make $JOBS -s ${{ matrix.test_task }}
env:
RUBY_TESTOPTS: "-q --tty=no"
- # not sure why ~/.gnupg is remained
- # see tool/test/test_sync_default_gems.rb
- - name: Forcibly remove test directory
+ # test_sync_default_gems triggers gpg, whose agent processes leave
+ # $HOME/.gnupg around even when GNUPGHOME points elsewhere.
+ - name: Forcibly remove ~/.gnupg
run: rm -rf $HOME/.gnupg
- if: inputs.remove-gnupg
- name: Diff stats of HOME
run: |
set -euxo pipefail
- cd "${{ inputs.archname }}/"
+ cd "$ARCHNAME/"
cat >test.rb <<'EOF'
require 'pathname'
require 'digest'
@@ -227,18 +128,19 @@ jobs:
diff -u /tmp/stat-before-tests.txt /tmp/stat-after-tests.txt
# leaked-globals since 2.7
- name: Leaked Globals
- run: cd "${{ inputs.archname }}/" && make -s leaked-globals
+ run: cd "$ARCHNAME/" && make -s leaked-globals
if: matrix.test_task == 'check'
- name: make install without root privilege
- run: cd "${{ inputs.archname }}/" && make $JOBS install DESTDIR="/tmp/destdir"
+ run: cd "$ARCHNAME/" && make $JOBS install DESTDIR="/tmp/destdir"
if: matrix.test_task == 'check'
- name: make install
- run: cd "${{ inputs.archname }}/" && sudo make $JOBS install
+ run: cd "$ARCHNAME/" && sudo make $JOBS install
if: matrix.test_task == 'check'
- - name: ruby -v
- env:
- RUBY_BIN: ${{ inputs.ruby-bin }}
- run: '"$RUBY_BIN" -v'
+ - name: Verify installed binaries
+ run: |
+ /usr/local/bin/ruby -v
+ /usr/local/bin/gem -v
+ /usr/local/bin/bundle -v
if: matrix.test_task == 'check'
- name: Show .local
run: find $HOME/.local -ls
@@ -246,28 +148,24 @@ jobs:
with:
payload: |
{
- "attachments": [{
- "text": "${{ job.status }}: ${{ matrix.os }} / ${{ matrix.test_task }} <https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|${{ github.workflow }}>",
- "color": "danger"
- }]
+ "ci": "GitHub Actions",
+ "env": "snapshot: ${{ matrix.os }} / ${{ matrix.test_task }}",
+ "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}",
+ "commit": "${{ github.sha }}",
+ "branch": "${{ github.ref_name }}"
}
env:
- SLACK_WEBHOOK_URL: ${{ secrets.SNAPSHOT_SLACK_WEBHOOK_URL }}
- if: failure() && github.event_name == 'schedule'
- - name: Get ruby/ruby sha
- id: ruby_sha
- run: cd "${{ inputs.archname }}/" && ./ruby -e 'puts "sha=#{RUBY_REVISION}"' >> $GITHUB_OUTPUT
- if: failure() && inputs.notify-ruby-sha && github.event_name == 'schedule'
+ SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }}
+ if: failure()
- uses: ruby/action-slack@54175162371f1f7c8eb94d7c8644ee2479fcd375 # v3.2.2
with:
payload: |
{
- "ci": "GitHub Actions",
- "env": "snapshot: ${{ matrix.os }} / ${{ matrix.test_task }}",
- "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}",
- "commit": "${{ steps.ruby_sha.outputs.sha }}",
- "branch": "${{ inputs.branch-label }}"
+ "attachments": [{
+ "text": "${{ job.status }}: ${{ matrix.os }} / ${{ matrix.test_task }} <https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|${{ github.workflow }}>",
+ "color": "danger"
+ }]
}
env:
- SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }}
- if: failure() && inputs.notify-ruby-sha && github.event_name == 'schedule'
+ SLACK_WEBHOOK_URL: ${{ secrets.SNAPSHOT_SLACK_WEBHOOK_URL }}
+ if: failure() && inputs.notify-release-channel
diff --git a/.github/workflows/tarball-windows.yml b/.github/workflows/tarball-windows.yml
index 52a28f006e..fb29253eae 100644
--- a/.github/workflows/tarball-windows.yml
+++ b/.github/workflows/tarball-windows.yml
@@ -7,11 +7,11 @@ on:
description: 'archname (e.g. snapshot-master)'
required: true
type: string
- patch-url:
- description: 'Patch URL forwarded from workflow_dispatch'
+ notify-release-channel:
+ description: 'Also send failure notifications to SNAPSHOT_SLACK_WEBHOOK_URL (schedule/release builds).'
required: false
- type: string
- default: ''
+ type: boolean
+ default: false
jobs:
windows:
@@ -73,18 +73,6 @@ jobs:
- name: Extract
run: 7z x pkg/*.zip
working-directory:
- - name: Apply patch
- run: |
- set -x
- curl -sSL "${RUBY_PATCH_URL}" -o ruby.patch
- cd "$ARCHNAME"
- git apply ../ruby.patch
- shell: bash
- env:
- RUBY_PATCH_URL: ${{ inputs.patch-url }}
- ARCHNAME: ${{ inputs.archname }}
- if: inputs.patch-url != ''
- working-directory:
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
@@ -131,8 +119,7 @@ jobs:
YACC: win_bison
- name: ruby -v
- run: |
- .\ruby -v
+ run: .\ruby -v
- run: nmake test
timeout-minutes: 5
@@ -147,6 +134,19 @@ jobs:
with:
payload: |
{
+ "ci": "GitHub Actions",
+ "env": "snapshot: ${{ env.OS_VER }} / ${{ matrix.test_task }}",
+ "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}",
+ "commit": "${{ github.sha }}",
+ "branch": "${{ github.ref_name }}"
+ }
+ env:
+ SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }}
+ if: failure()
+ - uses: ruby/action-slack@54175162371f1f7c8eb94d7c8644ee2479fcd375 # v3.2.2
+ with:
+ payload: |
+ {
"attachments": [{
"text": "${{ job.status }}: ${{ env.OS_VER }} / ${{ matrix.test_task }} <https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|${{ github.workflow }}>",
"color": "danger"
@@ -154,4 +154,4 @@ jobs:
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SNAPSHOT_SLACK_WEBHOOK_URL }}
- if: failure() && github.event_name == 'schedule'
+ if: failure() && inputs.notify-release-channel
diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb
index b40c908f6f..4fe90703fc 100644
--- a/bootstraptest/test_ractor.rb
+++ b/bootstraptest/test_ractor.rb
@@ -2643,3 +2643,24 @@ rescue ThreadError => e
end
end
RUBY
+
+# Concurrent super calls with keyword arguments must not race on the
+# callinfo kwarg reference count. [Bug #22075]
+assert_equal 'ok', %q{
+ class Base
+ def foo(a:, b:, c:) = a
+ end
+
+ class Sub < Base
+ def foo(a:, b:, c:) = super(a: a, b: b, c: c)
+ end
+
+ 4.times.map do
+ Ractor.new do
+ obj = Sub.new
+ 100_000.times { obj.foo(a: 1, b: 2, c: 3) }
+ end
+ end.each(&:join)
+
+ :ok
+}
diff --git a/common.mk b/common.mk
index 80b6547de1..916484a6fe 100644
--- a/common.mk
+++ b/common.mk
@@ -69,6 +69,7 @@ LIBRUBY_EXTS = ./.libruby-with-ext.time
REVISION_H = ./.revision.time
PLATFORM_D = $(TIMESTAMPDIR)/.$(PLATFORM_DIR).time
ENC_TRANS_D = $(TIMESTAMPDIR)/.enc-trans.time
+yes_cross_compiling = $(CROSS_COMPILING:no=)
X_$(CROSS_COMPILING:yes=)BASERUBY = $(BASERUBY)
X_$(CROSS_COMPILING:no=)BASERUBY = $(XRUBY)
RDOC = $(X_BASERUBY) --enable-gems "$(tooldir)/rdoc-srcdir"
@@ -352,7 +353,8 @@ ext/configure-ext.mk: $(PREP) all-incs $(MKFILES) $(RBCONFIG) $(LIBRUBY) \
$(Q)$(MAKEDIRS) $(@D)
$(Q)$(MINIRUBY) $(tooldir)/generic_erb.rb -o $@ -c \
$(srcdir)/template/$(@F).tmpl --srcdir="$(srcdir)" \
- --miniruby="$(MINIRUBY)" --script-args='$(SCRIPT_ARGS)'
+ --miniruby="$(MINIRUBY)" --script-args='$(SCRIPT_ARGS)' \
+ $(yes_cross_compiling:yes=--without-ext=-test-)
configure-ext: $(EXTS_MK)
diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c
index 7e21a21f7e..936fb0f81b 100644
--- a/ext/strscan/strscan.c
+++ b/ext/strscan/strscan.c
@@ -822,7 +822,7 @@ strscan_scan(VALUE self, VALUE re)
* :include: strscan/link_refs.txt
*
* call-seq:
- * match?(pattern) -> updated_position or nil
+ * match?(pattern) -> match_size or nil
*
* Attempts to [match][17] the given `pattern`
* at the beginning of the [target substring][3];
@@ -882,7 +882,7 @@ strscan_match_p(VALUE self, VALUE re)
/*
* :markup: markdown
* call-seq:
- * skip(pattern) match_size or nil
+ * skip(pattern) -> match_size or nil
*
* :include: strscan/link_refs.txt
* :include: strscan/methods/skip.md
@@ -956,7 +956,7 @@ strscan_check(VALUE self, VALUE re)
/*
* call-seq:
- * scan_full(pattern, advance_pointer_p, return_string_p) -> matched_substring or nil
+ * scan_full(pattern, advance_pointer_p, return_string_p) -> matched_substring or length or nil
*
* Equivalent to one of the following:
*
@@ -1202,7 +1202,7 @@ strscan_getch(VALUE self)
/*
* call-seq:
- * scan_byte -> integer_byte
+ * scan_byte -> integer_byte or nil
*
* Scans one byte and returns it as an integer.
* This method is not multibyte character sensitive.
diff --git a/gc.c b/gc.c
index 6f49973148..168087d914 100644
--- a/gc.c
+++ b/gc.c
@@ -3927,10 +3927,34 @@ rb_gc_update_tbl_refs(st_table *ptr)
gc_update_table_refs(ptr);
}
+static int
+rb_gc_update_set_refs_i(st_data_t key, st_data_t value, st_data_t argp, int error)
+{
+ if (rb_gc_location((VALUE)key) != (VALUE)key) {
+ return ST_REPLACE;
+ }
+
+ return ST_CONTINUE;
+}
+
+static int
+rb_gc_update_set_refs_replace_i(st_data_t *key, st_data_t *value, st_data_t argp, int existing)
+{
+ if (rb_gc_location((VALUE)*key) != (VALUE)*key) {
+ *key = rb_gc_location((VALUE)*key);
+ }
+
+ return ST_CONTINUE;
+}
+
void
-rb_gc_update_set_refs(st_table *ptr)
+rb_gc_update_set_refs(st_table *tbl)
{
- gc_update_set_refs(ptr);
+ if (!tbl || tbl->num_entries == 0) return;
+
+ if (st_foreach_with_replace(tbl, rb_gc_update_set_refs_i, rb_gc_update_set_refs_replace_i, 0)) {
+ rb_raise(rb_eRuntimeError, "hash modified during iteration");
+ }
}
static void
diff --git a/gc/default/default.c b/gc/default/default.c
index e47c31d08d..291ff91b81 100644
--- a/gc/default/default.c
+++ b/gc/default/default.c
@@ -23,6 +23,7 @@
#include "ruby/ruby.h"
#include "ruby/atomic.h"
+#include "ruby_atomic.h"
#include "ruby/debug.h"
#include "ruby/thread.h"
#include "ruby/util.h"
@@ -196,18 +197,6 @@ static RB_THREAD_LOCAL_SPECIFIER int malloc_increase_local;
SLOT(32) SLOT(64) SLOT(128) SLOT(256) SLOT(512)
#endif
-/* Precomputed reciprocals for fast slot index calculation.
- * For slot size d: reciprocal = ceil(2^48 / d).
- * Then offset / d == (uint32_t)((offset * reciprocal) >> 48)
- * for all offset < HEAP_PAGE_SIZE. */
-#define SLOT_RECIPROCAL_SHIFT 48
-#define SLOT_RECIPROCAL(size) (((1ULL << SLOT_RECIPROCAL_SHIFT) + (size) - 1) / (size))
-
-static const uint64_t heap_slot_reciprocal_table[HEAP_COUNT] = {
-#define SLOT(size) SLOT_RECIPROCAL(size),
- EACH_POOL_SLOT_SIZE(SLOT)
-#undef SLOT
-};
typedef struct ractor_newobj_heap_cache {
struct free_slot *freelist;
struct heap_page *using_page;
@@ -507,11 +496,30 @@ enum gc_mode {
gc_mode_compacting,
};
+typedef rbimpl_atomic_uint64_t gc_counter_t;
+
+#if !defined(HAVE_GCC_ATOMIC_BUILTINS_64) && !defined(_WIN32) && \
+ !(defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)))
+# define MALLOC_COUNTERS_NEED_LOCK 1
+#endif
+
+struct gc_malloc_bytes {
+ gc_counter_t malloc;
+ gc_counter_t free;
+
+ /* Snapshots of `malloc` / `free` taken at the end of the last GC */
+ gc_counter_t malloc_at_last_gc;
+ gc_counter_t free_at_last_gc;
+};
+
typedef struct rb_objspace {
struct {
- size_t increase;
+ struct gc_malloc_bytes counters;
#if RGENGC_ESTIMATE_OLDMALLOC
- size_t oldmalloc_increase;
+ struct gc_malloc_bytes oldcounters;
+#endif
+#ifdef MALLOC_COUNTERS_NEED_LOCK
+ rb_nativethread_lock_t lock;
#endif
} malloc_counters;
@@ -711,11 +719,23 @@ size_t rb_gc_impl_obj_slot_size(VALUE obj);
#define RVALUE_SLOT_SIZE (sizeof(struct RBasic) + sizeof(VALUE[RBIMPL_RVALUE_EMBED_LEN_MAX]) + RVALUE_OVERHEAD)
static const size_t pool_slot_sizes[HEAP_COUNT] = {
-#define SLOT(size) size,
+#define SLOT(size) ((size) + RVALUE_OVERHEAD),
EACH_POOL_SLOT_SIZE(SLOT)
#undef SLOT
};
+/* Precomputed reciprocals for fast slot index calculation.
+ * For slot size d: reciprocal = ceil(2^48 / d).
+ * Then offset / d == (uint32_t)((offset * reciprocal) >> 48)
+ * for all offset < HEAP_PAGE_SIZE. */
+#define SLOT_RECIPROCAL_SHIFT 48
+#define SLOT_RECIPROCAL(size) (((1ULL << SLOT_RECIPROCAL_SHIFT) + (size) - 1) / (size))
+
+static const uint64_t heap_slot_reciprocal_table[HEAP_COUNT] = {
+#define SLOT(size) SLOT_RECIPROCAL((size) + RVALUE_OVERHEAD),
+ EACH_POOL_SLOT_SIZE(SLOT)
+#undef SLOT
+};
#if SIZEOF_VALUE >= 8
static uint8_t size_to_heap_idx[1024 / 8 + 1];
@@ -943,8 +963,112 @@ RVALUE_AGE_SET(VALUE obj, int age)
}
#define malloc_limit objspace->malloc_params.limit
-#define malloc_increase objspace->malloc_counters.increase
+#define malloc_increase gc_malloc_counters_increase_unsigned(objspace, &objspace->malloc_counters.counters)
#define malloc_allocated_size objspace->malloc_params.allocated_size
+
+#ifdef MALLOC_COUNTERS_NEED_LOCK
+# define MALLOC_COUNTERS_LOCK(o) rb_native_mutex_lock(&(o)->malloc_counters.lock)
+# define MALLOC_COUNTERS_UNLOCK(o) rb_native_mutex_unlock(&(o)->malloc_counters.lock)
+#else
+# define MALLOC_COUNTERS_LOCK(o) ((void)0)
+# define MALLOC_COUNTERS_UNLOCK(o) ((void)0)
+#endif
+
+static inline void
+gc_counter_add(gc_counter_t *p, size_t delta)
+{
+#ifdef MALLOC_COUNTERS_NEED_LOCK
+ *p += (gc_counter_t)delta;
+#else
+ rbimpl_atomic_u64_fetch_add_relaxed(p, (uint64_t)delta);
+#endif
+}
+
+static inline gc_counter_t
+gc_counter_load_relaxed(const gc_counter_t *p)
+{
+#ifdef MALLOC_COUNTERS_NEED_LOCK
+ return *p;
+#else
+ return rbimpl_atomic_u64_load_relaxed(p);
+#endif
+}
+
+static inline gc_counter_t
+gc_counter_load_acquire(const gc_counter_t *p)
+{
+#ifdef MALLOC_COUNTERS_NEED_LOCK
+ return *p;
+#else
+ return rbimpl_atomic_u64_load_acquire(p);
+#endif
+}
+
+static inline void
+gc_counter_store_release(gc_counter_t *p, gc_counter_t v)
+{
+#ifdef MALLOC_COUNTERS_NEED_LOCK
+ *p = v;
+#else
+ rbimpl_atomic_u64_set_release(p, v);
+#endif
+}
+
+static inline int64_t
+gc_malloc_counters_increase(rb_objspace_t *objspace, const struct gc_malloc_bytes *c)
+{
+ MALLOC_COUNTERS_LOCK(objspace);
+ gc_counter_t malloc_at = gc_counter_load_acquire(&c->malloc_at_last_gc);
+ gc_counter_t free_at = gc_counter_load_acquire(&c->free_at_last_gc);
+ gc_counter_t malloc_now = gc_counter_load_relaxed(&c->malloc);
+ gc_counter_t free_now = gc_counter_load_relaxed(&c->free);
+ MALLOC_COUNTERS_UNLOCK(objspace);
+
+ gc_counter_t malloc_delta = malloc_now - malloc_at;
+ gc_counter_t free_delta = free_now - free_at;
+
+ if (malloc_delta >= free_delta) {
+ return (int64_t)(malloc_delta - free_delta);
+ }
+ else {
+ return -(int64_t)(free_delta - malloc_delta);
+ }
+}
+
+static inline size_t
+gc_malloc_counters_increase_unsigned(rb_objspace_t *objspace, const struct gc_malloc_bytes *c)
+{
+ int64_t inc = gc_malloc_counters_increase(objspace, c);
+ if (inc <= 0) return 0;
+#if SIZEOF_SIZE_T < 8
+ if ((uint64_t)inc > SIZE_MAX) return SIZE_MAX;
+#endif
+ return (size_t)inc;
+}
+
+static inline int64_t
+gc_malloc_counters_snapshot(rb_objspace_t *objspace, struct gc_malloc_bytes *c)
+{
+ MALLOC_COUNTERS_LOCK(objspace);
+ gc_counter_t malloc_now = gc_counter_load_relaxed(&c->malloc);
+ gc_counter_t free_now = gc_counter_load_relaxed(&c->free);
+ gc_counter_t malloc_at = gc_counter_load_relaxed(&c->malloc_at_last_gc);
+ gc_counter_t free_at = gc_counter_load_relaxed(&c->free_at_last_gc);
+ gc_counter_store_release(&c->malloc_at_last_gc, malloc_now);
+ gc_counter_store_release(&c->free_at_last_gc, free_now);
+ MALLOC_COUNTERS_UNLOCK(objspace);
+
+ gc_counter_t malloc_delta = malloc_now - malloc_at;
+ gc_counter_t free_delta = free_now - free_at;
+
+ if (malloc_delta >= free_delta) {
+ return (int64_t)(malloc_delta - free_delta);
+ }
+ else {
+ return -(int64_t)(free_delta - malloc_delta);
+ }
+}
+
#define heap_pages_lomem objspace->heap_pages.range[0]
#define heap_pages_himem objspace->heap_pages.range[1]
#define heap_pages_freeable_pages objspace->heap_pages.freeable_pages
@@ -3897,6 +4021,13 @@ gc_sweep_finish(rb_objspace_t *objspace)
}
}
+ (void)gc_malloc_counters_snapshot(objspace, &objspace->malloc_counters.counters);
+#if RGENGC_ESTIMATE_OLDMALLOC
+ if (objspace->profile.latest_gc_info & GPR_FLAG_MAJOR_MASK) {
+ (void)gc_malloc_counters_snapshot(objspace, &objspace->malloc_counters.oldcounters);
+ }
+#endif
+
rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_END_SWEEP);
gc_mode_transition(objspace, gc_mode_none);
@@ -4924,10 +5055,22 @@ gc_check_after_marks_i(st_data_t k, st_data_t v, st_data_t ptr)
static void
gc_marks_check(rb_objspace_t *objspace, st_foreach_callback_func *checker_func, const char *checker_name)
{
- size_t saved_malloc_increase = objspace->malloc_params.increase;
+ MALLOC_COUNTERS_LOCK(objspace);
+ struct gc_malloc_bytes saved_malloc = {
+ .malloc = gc_counter_load_relaxed(&objspace->malloc_counters.counters.malloc),
+ .free = gc_counter_load_relaxed(&objspace->malloc_counters.counters.free),
+ .malloc_at_last_gc = gc_counter_load_relaxed(&objspace->malloc_counters.counters.malloc_at_last_gc),
+ .free_at_last_gc = gc_counter_load_relaxed(&objspace->malloc_counters.counters.free_at_last_gc),
+ };
#if RGENGC_ESTIMATE_OLDMALLOC
- size_t saved_oldmalloc_increase = objspace->malloc_counters.oldmalloc_increase;
+ struct gc_malloc_bytes saved_oldmalloc = {
+ .malloc = gc_counter_load_relaxed(&objspace->malloc_counters.oldcounters.malloc),
+ .free = gc_counter_load_relaxed(&objspace->malloc_counters.oldcounters.free),
+ .malloc_at_last_gc = gc_counter_load_relaxed(&objspace->malloc_counters.oldcounters.malloc_at_last_gc),
+ .free_at_last_gc = gc_counter_load_relaxed(&objspace->malloc_counters.oldcounters.free_at_last_gc),
+ };
#endif
+ MALLOC_COUNTERS_UNLOCK(objspace);
VALUE already_disabled = rb_objspace_gc_disable(objspace);
objspace->rgengc.allrefs_table = objspace_allrefs(objspace);
@@ -4947,10 +5090,18 @@ gc_marks_check(rb_objspace_t *objspace, st_foreach_callback_func *checker_func,
objspace->rgengc.allrefs_table = 0;
if (already_disabled == Qfalse) rb_objspace_gc_enable(objspace);
- objspace->malloc_params.increase = saved_malloc_increase;
+ MALLOC_COUNTERS_LOCK(objspace);
+ gc_counter_store_release(&objspace->malloc_counters.counters.malloc, saved_malloc.malloc);
+ gc_counter_store_release(&objspace->malloc_counters.counters.free, saved_malloc.free);
+ gc_counter_store_release(&objspace->malloc_counters.counters.malloc_at_last_gc, saved_malloc.malloc_at_last_gc);
+ gc_counter_store_release(&objspace->malloc_counters.counters.free_at_last_gc, saved_malloc.free_at_last_gc);
#if RGENGC_ESTIMATE_OLDMALLOC
- objspace->malloc_counters.oldmalloc_increase = saved_oldmalloc_increase;
+ gc_counter_store_release(&objspace->malloc_counters.oldcounters.malloc, saved_oldmalloc.malloc);
+ gc_counter_store_release(&objspace->malloc_counters.oldcounters.free, saved_oldmalloc.free);
+ gc_counter_store_release(&objspace->malloc_counters.oldcounters.malloc_at_last_gc, saved_oldmalloc.malloc_at_last_gc);
+ gc_counter_store_release(&objspace->malloc_counters.oldcounters.free_at_last_gc, saved_oldmalloc.free_at_last_gc);
#endif
+ MALLOC_COUNTERS_UNLOCK(objspace);
}
#endif /* RGENGC_CHECK_MODE >= 4 */
@@ -6310,11 +6461,14 @@ gc_reset_malloc_info(rb_objspace_t *objspace, bool full_mark)
{
gc_prof_set_malloc_info(objspace);
{
- size_t inc = RUBY_ATOMIC_SIZE_EXCHANGE(malloc_increase, 0);
+ int64_t inc = gc_malloc_counters_increase(objspace, &objspace->malloc_counters.counters);
size_t old_limit = malloc_limit;
- if (inc > malloc_limit) {
- malloc_limit = (size_t)(inc * gc_params.malloc_limit_growth_factor);
+ /* A net-negative `inc` (more freed than malloc'd since last GC) is
+ * treated the same as "allocated less than malloc_limit".
+ * This matches what we were doing pre-monotonic counters, but is it right? */
+ if (inc > 0 && (size_t)inc > malloc_limit) {
+ malloc_limit = (size_t)((size_t)inc * gc_params.malloc_limit_growth_factor);
if (malloc_limit > gc_params.malloc_limit_max) {
malloc_limit = gc_params.malloc_limit_max;
}
@@ -6341,7 +6495,11 @@ gc_reset_malloc_info(rb_objspace_t *objspace, bool full_mark)
/* reset oldmalloc info */
#if RGENGC_ESTIMATE_OLDMALLOC
if (!full_mark) {
- if (objspace->malloc_counters.oldmalloc_increase > objspace->rgengc.oldmalloc_increase_limit) {
+ /* Don't snapshot on minor GC: oldmalloc_increase is meant to
+ * accumulate across minor GCs and only reset at major GC. */
+ int64_t oldmalloc_increase = gc_malloc_counters_increase(objspace, &objspace->malloc_counters.oldcounters);
+ if (oldmalloc_increase > 0 &&
+ (uint64_t)oldmalloc_increase > objspace->rgengc.oldmalloc_increase_limit) {
gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_OLDMALLOC;
objspace->rgengc.oldmalloc_increase_limit =
(size_t)(objspace->rgengc.oldmalloc_increase_limit * gc_params.oldmalloc_limit_growth_factor);
@@ -6351,17 +6509,14 @@ gc_reset_malloc_info(rb_objspace_t *objspace, bool full_mark)
}
}
- if (0) fprintf(stderr, "%"PRIdSIZE"\t%d\t%"PRIuSIZE"\t%"PRIuSIZE"\t%"PRIdSIZE"\n",
+ if (0) fprintf(stderr, "%"PRIdSIZE"\t%d\t%"PRId64"\t%"PRIuSIZE"\t%"PRIdSIZE"\n",
rb_gc_count(),
gc_needs_major_flags,
- objspace->malloc_counters.oldmalloc_increase,
+ oldmalloc_increase,
objspace->rgengc.oldmalloc_increase_limit,
gc_params.oldmalloc_limit_max);
}
else {
- /* major GC */
- objspace->malloc_counters.oldmalloc_increase = 0;
-
if ((objspace->profile.latest_gc_info & GPR_FLAG_MAJOR_BY_OLDMALLOC) == 0) {
objspace->rgengc.oldmalloc_increase_limit =
(size_t)(objspace->rgengc.oldmalloc_increase_limit / ((gc_params.oldmalloc_limit_growth_factor - 1)/10 + 1));
@@ -7460,6 +7615,8 @@ enum gc_stat_sym {
gc_stat_sym_total_freed_pages,
gc_stat_sym_total_allocated_objects,
gc_stat_sym_total_freed_objects,
+ gc_stat_sym_total_malloc_bytes,
+ gc_stat_sym_total_free_bytes,
gc_stat_sym_malloc_increase_bytes,
gc_stat_sym_malloc_increase_bytes_limit,
gc_stat_sym_minor_gc_count,
@@ -7510,6 +7667,8 @@ setup_gc_stat_symbols(void)
S(total_freed_pages);
S(total_allocated_objects);
S(total_freed_objects);
+ S(total_malloc_bytes);
+ S(total_free_bytes);
S(malloc_increase_bytes);
S(malloc_increase_bytes_limit);
S(minor_gc_count);
@@ -7571,27 +7730,32 @@ rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym)
return SIZET2NUM(attr); \
else if (hash != Qnil) \
rb_hash_aset(hash, gc_stat_symbols[gc_stat_sym_##name], SIZET2NUM(attr));
+#define SET64(name, attr) \
+ if (key == gc_stat_symbols[gc_stat_sym_##name]) \
+ return ULL2NUM(attr); \
+ else if (hash != Qnil) \
+ rb_hash_aset(hash, gc_stat_symbols[gc_stat_sym_##name], ULL2NUM(attr));
SET(count, objspace->profile.count);
SET(time, (size_t)ns_to_ms(objspace->profile.marking_time_ns + objspace->profile.sweeping_time_ns)); // TODO: UINT64T2NUM
SET(marking_time, (size_t)ns_to_ms(objspace->profile.marking_time_ns));
SET(sweeping_time, (size_t)ns_to_ms(objspace->profile.sweeping_time_ns));
- /* implementation dependent counters */
+ {
+ uint64_t total_malloc = (uint64_t)gc_counter_load_relaxed(&objspace->malloc_counters.counters.malloc);
+ uint64_t total_free = (uint64_t)gc_counter_load_relaxed(&objspace->malloc_counters.counters.free);
+ SET64(total_malloc_bytes, total_malloc);
+ SET64(total_free_bytes, total_free);
+ }
+
+ /* implementation dependent counters (small / fixnum-safe) */
SET(heap_allocated_pages, rb_darray_size(objspace->heap_pages.sorted));
SET(heap_empty_pages, objspace->empty_pages_count)
SET(heap_allocatable_bytes, objspace->heap_pages.allocatable_bytes);
- SET(heap_available_slots, objspace_available_slots(objspace));
- SET(heap_live_slots, objspace_live_slots(objspace));
- SET(heap_free_slots, objspace_free_slots(objspace));
- SET(heap_final_slots, total_final_slots_count(objspace));
- SET(heap_marked_slots, objspace->marked_slots);
SET(heap_eden_pages, heap_eden_total_pages(objspace));
SET(total_allocated_pages, objspace->heap_pages.allocated_pages);
SET(total_freed_pages, objspace->heap_pages.freed_pages);
- SET(total_allocated_objects, total_allocated_objects(objspace));
- SET(total_freed_objects, total_freed_objects(objspace));
- SET(malloc_increase_bytes, malloc_increase);
+ SET(malloc_increase_bytes, gc_malloc_counters_increase_unsigned(objspace, &objspace->malloc_counters.counters));
SET(malloc_increase_bytes_limit, malloc_limit);
SET(minor_gc_count, objspace->profile.minor_gc_count);
SET(major_gc_count, objspace->profile.major_gc_count);
@@ -7603,10 +7767,19 @@ rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym)
SET(old_objects, objspace->rgengc.old_objects);
SET(old_objects_limit, objspace->rgengc.old_objects_limit);
#if RGENGC_ESTIMATE_OLDMALLOC
- SET(oldmalloc_increase_bytes, objspace->malloc_counters.oldmalloc_increase);
+ SET(oldmalloc_increase_bytes, gc_malloc_counters_increase_unsigned(objspace, &objspace->malloc_counters.oldcounters));
SET(oldmalloc_increase_bytes_limit, objspace->rgengc.oldmalloc_increase_limit);
#endif
+ ractor_cache_flush_count(objspace, rb_gc_get_ractor_newobj_cache());
+ SET(total_allocated_objects, total_allocated_objects(objspace));
+ SET(total_freed_objects, total_freed_objects(objspace));
+ SET(heap_available_slots, objspace_available_slots(objspace));
+ SET(heap_live_slots, objspace_live_slots(objspace));
+ SET(heap_free_slots, objspace_free_slots(objspace));
+ SET(heap_final_slots, total_final_slots_count(objspace));
+ SET(heap_marked_slots, objspace->marked_slots);
+
#if RGENGC_PROFILE
SET(total_generated_normal_object_count, objspace->profile.total_generated_normal_object_count);
SET(total_generated_shady_object_count, objspace->profile.total_generated_shady_object_count);
@@ -7616,6 +7789,7 @@ rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym)
SET(total_remembered_shady_object_count, objspace->profile.total_remembered_shady_object_count);
#endif /* RGENGC_PROFILE */
#undef SET
+#undef SET64
if (!NIL_P(key)) {
// Matched key should return above
@@ -8044,16 +8218,22 @@ static void
malloc_increase_commit(rb_objspace_t *objspace, size_t new_size, size_t old_size)
{
if (new_size > old_size) {
- RUBY_ATOMIC_SIZE_ADD(malloc_increase, new_size - old_size);
+ size_t delta = new_size - old_size;
+ MALLOC_COUNTERS_LOCK(objspace);
+ gc_counter_add(&objspace->malloc_counters.counters.malloc, delta);
#if RGENGC_ESTIMATE_OLDMALLOC
- RUBY_ATOMIC_SIZE_ADD(objspace->malloc_counters.oldmalloc_increase, new_size - old_size);
+ gc_counter_add(&objspace->malloc_counters.oldcounters.malloc, delta);
#endif
+ MALLOC_COUNTERS_UNLOCK(objspace);
}
- else {
- atomic_sub_nounderflow(&malloc_increase, old_size - new_size);
+ else if (old_size > new_size) {
+ size_t delta = old_size - new_size;
+ MALLOC_COUNTERS_LOCK(objspace);
+ gc_counter_add(&objspace->malloc_counters.counters.free, delta);
#if RGENGC_ESTIMATE_OLDMALLOC
- atomic_sub_nounderflow(&objspace->malloc_counters.oldmalloc_increase, old_size - new_size);
+ gc_counter_add(&objspace->malloc_counters.oldcounters.free, delta);
#endif
+ MALLOC_COUNTERS_UNLOCK(objspace);
}
}
@@ -9390,6 +9570,10 @@ rb_gc_impl_objspace_free(void *objspace_ptr)
rb_darray_free_without_gc(objspace->weak_references);
+#ifdef MALLOC_COUNTERS_NEED_LOCK
+ rb_native_mutex_destroy(&objspace->malloc_counters.lock);
+#endif
+
free(objspace);
}
@@ -9520,6 +9704,9 @@ rb_gc_impl_objspace_init(void *objspace_ptr)
objspace->flags.measure_gc = true;
malloc_limit = gc_params.malloc_limit_min;
+#ifdef MALLOC_COUNTERS_NEED_LOCK
+ rb_native_mutex_initialize(&objspace->malloc_counters.lock);
+#endif
objspace->finalize_deferred_pjob = rb_postponed_job_preregister(0, gc_finalize_deferred, objspace);
if (objspace->finalize_deferred_pjob == POSTPONED_JOB_HANDLE_INVALID) {
rb_bug("Could not preregister postponed job for GC");
diff --git a/gc/gc.h b/gc/gc.h
index 69e0e0b780..ea8056c671 100644
--- a/gc/gc.h
+++ b/gc/gc.h
@@ -240,36 +240,6 @@ gc_update_table_refs(st_table *tbl)
}
}
-static int
-rb_set_foreach_replace(st_data_t key, st_data_t value, st_data_t argp, int error)
-{
- if (rb_gc_location((VALUE)key) != (VALUE)key) {
- return ST_REPLACE;
- }
-
- return ST_CONTINUE;
-}
-
-static int
-rb_set_replace_ref(st_data_t *key, st_data_t *value, st_data_t argp, int existing)
-{
- if (rb_gc_location((VALUE)*key) != (VALUE)*key) {
- *key = rb_gc_location((VALUE)*key);
- }
-
- return ST_CONTINUE;
-}
-
-static void
-gc_update_set_refs(st_table *tbl)
-{
- if (!tbl || tbl->num_entries == 0) return;
-
- if (st_foreach_with_replace(tbl, rb_set_foreach_replace, rb_set_replace_ref, 0)) {
- rb_raise(rb_eRuntimeError, "hash modified during iteration");
- }
-}
-
static inline size_t
xmalloc2_size(const size_t count, const size_t elsize)
{
diff --git a/imemo.c b/imemo.c
index 5c4c439381..3448a8dcd3 100644
--- a/imemo.c
+++ b/imemo.c
@@ -129,6 +129,7 @@ VALUE
rb_imemo_cdhash_new(size_t size, const struct st_hash_type *type)
{
struct rb_imemo_cdhash *memo = IMEMO_NEW(struct rb_imemo_cdhash, imemo_cdhash, 0);
+ memo->tbl.num_entries = 0;
st_init_existing_table_with_size(&memo->tbl, type, size);
return (VALUE)memo;
}
@@ -648,8 +649,7 @@ rb_imemo_free(VALUE obj)
const struct rb_callinfo *ci = ((const struct rb_callinfo *)obj);
if (ci->kwarg) {
- ((struct rb_callinfo_kwarg *)ci->kwarg)->references--;
- if (ci->kwarg->references == 0) {
+ if (RUBY_ATOMIC_FETCH_SUB(((struct rb_callinfo_kwarg *)ci->kwarg)->references, 1) == 1) {
ruby_xfree_sized((void *)ci->kwarg, rb_callinfo_kwarg_bytes(ci->kwarg->keyword_len));
}
}
diff --git a/pathname_builtin.rb b/pathname_builtin.rb
index 7a7befd664..51b6fd6a07 100644
--- a/pathname_builtin.rb
+++ b/pathname_builtin.rb
@@ -577,31 +577,32 @@ class Pathname
nil
end
- # Iterates over and yields a new Pathname object
- # for each element in the given path in descending order.
- #
- # Pathname.new('/path/to/some/file.rb').descend {|v| p v}
- # #<Pathname:/>
- # #<Pathname:/path>
- # #<Pathname:/path/to>
- # #<Pathname:/path/to/some>
- # #<Pathname:/path/to/some/file.rb>
- #
- # Pathname.new('path/to/some/file.rb').descend {|v| p v}
- # #<Pathname:path>
- # #<Pathname:path/to>
- # #<Pathname:path/to/some>
- # #<Pathname:path/to/some/file.rb>
+ # :markup: markdown
#
- # Returns an Enumerator if no block was given.
+ # call-seq:
+ # descend {|entry| ... } -> nil
+ # descend -> new_enumerator
#
- # enum = Pathname.new("/usr/bin/ruby").descend
- # # ... do stuff ...
- # enum.each { |e| ... }
- # # yields Pathnames /, /usr, /usr/bin, and /usr/bin/ruby.
+ # With a block given, yields a new pathname for each successive dirname
+ # in the stored path; see File.dirname:
#
- # It doesn't access the filesystem.
+ # ```ruby
+ # # Absolute path.
+ # Pathname('/path/to/some/file.rb').descend {|pn| p pn }
+ # #<Pathname:/>
+ # #<Pathname:/path>
+ # #<Pathname:/path/to>
+ # #<Pathname:/path/to/some>
+ # #<Pathname:/path/to/some/file.rb>
+ # # Relative path.
+ # Pathname('path/to/some/file.rb').descend {|pn| p pn }
+ # #<Pathname:path>
+ # #<Pathname:path/to>
+ # #<Pathname:path/to/some>
+ # #<Pathname:path/to/some/file.rb>
+ # ```
#
+ # With no block given, returns a new Enumerator.
def descend
return to_enum(__method__) unless block_given?
vs = []
@@ -1034,7 +1035,8 @@ class Pathname # * File *
# atime -> new_time
#
# Returns a new Time object containing the time of the most recent
- # access (read or write) to the entry represented by +self+:
+ # access (read or write) to the entry represented by `self`;
+ # see {File System Timestamps}[rdoc-ref:file/timestamps.md]:
#
# # Work in a temporary directory.
# require 'tmpdir'
@@ -1072,7 +1074,6 @@ class Pathname # * File *
# File atime: 2026-05-14 14:36:45 +0100
# Directory atime: 2026-05-14 14:36:45 +0100
#
- # See {File System Timestamps}[rdoc-ref:file/timestamps.md].
def atime() File.atime(@path) end
# :markup: markdown
@@ -1081,7 +1082,8 @@ class Pathname # * File *
# birthtime -> new_time
#
# Returns a new Time object containing the create time of the entry
- # represented by +self+:
+ # represented by `self`;
+ # see [File System Timestamps](rdoc-ref:file/timestamps.md):
#
# ```ruby
# # Work in a temporary directory.
@@ -1122,10 +1124,67 @@ class Pathname # * File *
# Directory birthtime: 2026-05-14 23:41:12 +0100
# ```
#
- # See [File System Timestamps](rdoc-ref:file/timestamps.md).
def birthtime() File.birthtime(@path) end
- # See <tt>File.ctime</tt>. Returns last (directory entry, not file) change time.
+ # :markup: markdown
+ #
+ # call-seq:
+ # ctime -> new_time
+ #
+ # On Windows, returns the #birthtime.
+ #
+ # On other systems,
+ # returns a new Time object containing the time of the most recent
+ # metadata change to the entry represented by `self`;
+ # see {File System Timestamps}[rdoc-ref:file/timestamps.md]:
+ #
+ # ```ruby
+ # # Work in a temporary directory.
+ # Pathname.mktmpdir do |tmpdirpath|
+ # # A subdirectory therein, and its Pathname.
+ # dirpath = File.join(tmpdirpath, 'subdir')
+ # dir_pn = Pathname(dirpath)
+ # puts "Create directory; directory ctime established."
+ # dir_pn.mkdir
+ # puts " Directory ctime: #{dir_pn.ctime}"
+ # sleep(1)
+ #
+ # # A file in the subdirectory, and its Pathname.
+ # filepath = File.join(dirpath, 't.txt')
+ # file_pn = Pathname(filepath)
+ # puts "Create file; file ctime established; directory ctime updated."
+ # file_pn.write('foo')
+ # puts " File ctime: #{file_pn.ctime}"
+ # puts " Directory ctime: #{dir_pn.ctime}"
+ # sleep(1)
+ # puts "Write file; file ctime updated; directory ctime not updated."
+ # file_pn.write('bar')
+ # puts " File ctime: #{file_pn.ctime}"
+ # puts " Directory ctime: #{dir_pn.ctime}"
+ # sleep(1)
+ # puts "Read file; neither ctime not updated."
+ # file_pn.read
+ # puts " File ctime: #{file_pn.ctime}"
+ # puts " Directory ctime: #{dir_pn.ctime}"
+ # end
+ # ```
+ #
+ # Output:
+ #
+ # ```text
+ # Create directory; directory ctime established.
+ # Directory ctime: 2026-05-20 14:05:05 -0500
+ # Create file; file ctime established; directory ctime updated.
+ # File ctime: 2026-05-20 14:05:06 -0500
+ # Directory ctime: 2026-05-20 14:05:06 -0500
+ # Write file; file ctime updated; directory ctime not updated.
+ # File ctime: 2026-05-20 14:05:07 -0500
+ # Directory ctime: 2026-05-20 14:05:06 -0500
+ # Read file; neither ctime not updated.
+ # File ctime: 2026-05-20 14:05:07 -0500
+ # Directory ctime: 2026-05-20 14:05:06 -0500
+ # ```
+ #
def ctime() File.ctime(@path) end
# See <tt>File.mtime</tt>. Returns last modification time.
@@ -1430,7 +1489,20 @@ class Pathname # * FileTest *
# See <tt>FileTest.grpowned?</tt>.
def grpowned?() FileTest.grpowned?(@path) end
- # See <tt>FileTest.directory?</tt>.
+ # :markup: markdown
+ #
+ # call-seq:
+ # directory? -> true or false
+ #
+ # Returns whether the entry represented by `self` is a directory:
+ #
+ # ```ruby
+ # Pathname('/etc').directory? # => true
+ # Pathname('lib').directory? # => true
+ # Pathname('README.md').directory? # => false
+ # Pathname('nosuch').directory? # => false
+ # ```
+ #
def directory?() FileTest.directory?(@path) end
# See <tt>FileTest.file?</tt>.
diff --git a/ruby_atomic.h b/ruby_atomic.h
index cbcfe682ce..409b9bcfd2 100644
--- a/ruby_atomic.h
+++ b/ruby_atomic.h
@@ -70,4 +70,44 @@ rbimpl_atomic_u64_set_relaxed(volatile rbimpl_atomic_uint64_t *address, uint64_t
}
#define ATOMIC_U64_SET_RELAXED(var, val) rbimpl_atomic_u64_set_relaxed(&(var), val)
+static inline uint64_t
+rbimpl_atomic_u64_fetch_add_relaxed(volatile rbimpl_atomic_uint64_t *value, uint64_t addend)
+{
+#if defined(HAVE_GCC_ATOMIC_BUILTINS_64)
+ return __atomic_fetch_add(value, addend, __ATOMIC_RELAXED);
+#elif defined(_WIN32)
+ return (uint64_t)InterlockedExchangeAdd64((LONG64 *)value, (LONG64)addend);
+#elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx))
+ return atomic_add_64_nv(value, addend) - addend;
+#else
+ // TODO: stdatomic
+ uint64_t prev = *value;
+ *value = prev + addend;
+ return prev;
+#endif
+}
+#define ATOMIC_U64_FETCH_ADD_RELAXED(var, val) rbimpl_atomic_u64_fetch_add_relaxed(&(var), val)
+
+static inline uint64_t
+rbimpl_atomic_u64_load_acquire(const volatile rbimpl_atomic_uint64_t *value)
+{
+#if defined(HAVE_GCC_ATOMIC_BUILTINS_64)
+ return __atomic_load_n(value, __ATOMIC_ACQUIRE);
+#else
+ return rbimpl_atomic_u64_load_relaxed(value);
+#endif
+}
+#define ATOMIC_U64_LOAD_ACQUIRE(var) rbimpl_atomic_u64_load_acquire(&(var))
+
+static inline void
+rbimpl_atomic_u64_set_release(volatile rbimpl_atomic_uint64_t *address, uint64_t value)
+{
+#if defined(HAVE_GCC_ATOMIC_BUILTINS_64)
+ __atomic_store_n(address, value, __ATOMIC_RELEASE);
+#else
+ rbimpl_atomic_u64_set_relaxed(address, value);
+#endif
+}
+#define ATOMIC_U64_SET_RELEASE(var, val) rbimpl_atomic_u64_set_release(&(var), val)
+
#endif
diff --git a/template/configure-ext.mk.tmpl b/template/configure-ext.mk.tmpl
index fff75b1f2e..b4e999fe8e 100644
--- a/template/configure-ext.mk.tmpl
+++ b/template/configure-ext.mk.tmpl
@@ -7,24 +7,34 @@ ECHO = $(ECHO1:0=@echo)
<%
srcdir = miniruby = script_args = nil
+with_exts = []
+without_exts = []
opt = OptionParser.new do |o|
o.on('--srcdir=SRCDIR') {|v| srcdir = v}
o.on('--miniruby=MINIRUBY') {|v| miniruby = v}
o.on('--script-args=MINIRUBY') {|v| script_args = v}
+ o.on('--with-ext=ext[,...]', Array) {|v| with_exts.concat(v)}
+ o.on('--without-ext=ext[,...]', Array) {|v| without_exts.concat(v)}
o.order!(ARGV)
end
srcdir ||= File.dirname(File.dirname(__FILE__))
+script_args ||= ""
+
+filter = proc do |ext|
+ next false unless with_exts.empty? or !with_exts.any? {|w| File.fnmatch?(w, ext)}
+ without_exts.empty? or !without_exts.any? {|w| File.fnmatch?(w, ext)}
+end
exts = {
exts: [
"--extstatic $(EXTSTATIC)",
- Dir.glob("ext/**/extconf.rb", base: srcdir).map do |d|
- d[%r[\Aext/[^/]+]]
+ Dir.glob("ext/**/extconf.rb", base: srcdir).filter_map do |d|
+ d if filter[File.basename(d = d[%r[\Aext/[^/]+]])]
end.uniq
],
gems: [
"--no-extstatic",
- Dir.glob(".bundle/gems/**/extconf.rb", base: srcdir).grep_v(/test/) do |d|
- d[%r[\A\.bundle/gems/[^/]+]]
+ Dir.glob(".bundle/gems/**/extconf.rb", base: srcdir).grep_v(/test/).filter_map do |d|
+ d if filter[File.basename(d = d[%r[\A\.bundle/gems/[^/]+]])]
end.uniq
],
}
@@ -32,7 +42,14 @@ exts = {
MINIRUBY = <%=miniruby%>
SCRIPT_ARGS = <%=script_args.gsub("#", "\\#").gsub(/\A|[\s"']\K--jobserver-auth=[^\s'"]*/, "")%>
EXTMK_ARGS = $(SCRIPT_ARGS) --gnumake=$(gnumake) --extflags="$(EXTLDFLAGS)" \
- --make-flags="MINIRUBY='$(MINIRUBY)'"
+ --make-flags="MINIRUBY='$(MINIRUBY)'" \
+<% unless with_exts.empty? -%>
+ --with-ext=<%= with_exts.join(',') %> \
+<% end -%>
+<% unless without_exts.empty? -%>
+ --without-ext=<%= without_exts.join(',') %> \
+<% end -%>
+ # EXTMK_ARGS
all: exts gems
exts:
diff --git a/test/fiber/test_thread.rb b/test/fiber/test_thread.rb
index 0247f330d9..4d2fbde9ed 100644
--- a/test/fiber/test_thread.rb
+++ b/test/fiber/test_thread.rb
@@ -156,16 +156,20 @@ class TestFiberThread < Test::Unit::TestCase
end
def test_thread_join_hang
+ inner = nil
thread = Thread.new do
scheduler = SleepingUnblockScheduler.new
Fiber.set_scheduler scheduler
Fiber.schedule do
- Thread.new{sleep(0.01)}.value
+ inner = Thread.new{sleep(0.01)}
+ inner.value
end
end
thread.join
+ ensure
+ inner&.join
end
end
diff --git a/test/ruby/test_defined.rb b/test/ruby/test_defined.rb
index db1fdc8e25..75ed1a7534 100644
--- a/test/ruby/test_defined.rb
+++ b/test/ruby/test_defined.rb
@@ -62,6 +62,34 @@ class TestDefined < Test::Unit::TestCase
f.bar(Class.new(Foo).new) { |v| assert(v, "inherited protected method") }
end
+ module ProtectedInModule
+ def m
+ :m
+ end
+ protected :m
+ def call_m(o)
+ o.m
+ end
+ def defined_m(o)
+ defined?(o.m)
+ end
+ end
+ class ProtectedIncluderA
+ include ProtectedInModule
+ end
+ class ProtectedIncluderB
+ include ProtectedInModule
+ end
+
+ def test_defined_protected_method_in_included_module
+ a = ProtectedIncluderA.new
+ b = ProtectedIncluderB.new
+ assert_equal(:m, a.call_m(a))
+ assert_equal(:m, a.call_m(b))
+ assert_equal("method", a.defined_m(a))
+ assert_equal("method", a.defined_m(b))
+ end
+
def test_defined_undefined_method
f = Foo.new
assert_nil(defined?(f.quux)) # undefined method
diff --git a/vm_callinfo.h b/vm_callinfo.h
index 06215978d6..9a6c69deae 100644
--- a/vm_callinfo.h
+++ b/vm_callinfo.h
@@ -47,7 +47,7 @@ enum vm_call_flag_bits {
struct rb_callinfo_kwarg {
int keyword_len;
- int references;
+ rb_atomic_t references;
VALUE keywords[];
};
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index 783246a7ef..75d5023566 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -5485,21 +5485,21 @@ vm_defined(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, rb_num_t op_
break;
case DEFINED_METHOD:{
VALUE klass = CLASS_OF(v);
- const rb_method_entry_t *me = rb_method_entry_with_refinements(klass, SYM2ID(obj), NULL);
+ const rb_callable_method_entry_t *cme = rb_callable_method_entry_with_refinements(klass, SYM2ID(obj), NULL);
- if (me) {
- switch (METHOD_ENTRY_VISI(me)) {
+ if (cme) {
+ switch (METHOD_ENTRY_VISI(cme)) {
case METHOD_VISI_PRIVATE:
break;
case METHOD_VISI_PROTECTED:
- if (!rb_obj_is_kind_of(GET_SELF(), rb_class_real(me->defined_class))) {
+ if (!rb_obj_is_kind_of(GET_SELF(), vm_defined_class_for_protected_call(cme))) {
break;
}
case METHOD_VISI_PUBLIC:
return true;
break;
default:
- rb_bug("vm_defined: unreachable: %u", (unsigned int)METHOD_ENTRY_VISI(me));
+ rb_bug("vm_defined: unreachable: %u", (unsigned int)METHOD_ENTRY_VISI(cme));
}
}
else {
diff --git a/vm_method.c b/vm_method.c
index 5ef47e97ac..5a99f684c0 100644
--- a/vm_method.c
+++ b/vm_method.c
@@ -668,7 +668,7 @@ rb_vm_ci_lookup(ID mid, unsigned int flag, unsigned int argc, const struct rb_ca
const struct rb_callinfo *ci = NULL;
if (kwarg) {
- ((struct rb_callinfo_kwarg *)kwarg)->references++;
+ RUBY_ATOMIC_FETCH_ADD(((struct rb_callinfo_kwarg *)kwarg)->references, 1);
}
struct rb_callinfo *new_ci = SHAREABLE_IMEMO_NEW(struct rb_callinfo, imemo_callinfo, (VALUE)kwarg);
diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs
index 392e393378..4ad88bb7d0 100644
--- a/yjit/src/cruby_bindings.inc.rs
+++ b/yjit/src/cruby_bindings.inc.rs
@@ -371,6 +371,7 @@ pub struct vm_ifunc {
pub data: *const ::std::os::raw::c_void,
pub argc: vm_ifunc_argc,
}
+pub type rb_atomic_t = ::std::os::raw::c_uint;
pub const METHOD_VISI_UNDEF: rb_method_visibility_t = 0;
pub const METHOD_VISI_PUBLIC: rb_method_visibility_t = 1;
pub const METHOD_VISI_PRIVATE: rb_method_visibility_t = 2;
@@ -660,7 +661,7 @@ pub type vm_call_flag_bits = u32;
#[repr(C)]
pub struct rb_callinfo_kwarg {
pub keyword_len: ::std::os::raw::c_int,
- pub references: ::std::os::raw::c_int,
+ pub references: rb_atomic_t,
pub keywords: __IncompleteArrayField<VALUE>,
}
#[repr(C)]
diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs
index 860e1726db..c61e61edd1 100644
--- a/zjit/src/cruby_bindings.inc.rs
+++ b/zjit/src/cruby_bindings.inc.rs
@@ -462,6 +462,7 @@ pub struct vm_ifunc {
pub data: *const ::std::os::raw::c_void,
pub argc: vm_ifunc_argc,
}
+pub type rb_atomic_t = ::std::os::raw::c_uint;
pub const METHOD_VISI_UNDEF: rb_method_visibility_t = 0;
pub const METHOD_VISI_PUBLIC: rb_method_visibility_t = 1;
pub const METHOD_VISI_PRIVATE: rb_method_visibility_t = 2;
@@ -1535,7 +1536,7 @@ pub type vm_call_flag_bits = u32;
#[repr(C)]
pub struct rb_callinfo_kwarg {
pub keyword_len: ::std::os::raw::c_int,
- pub references: ::std::os::raw::c_int,
+ pub references: rb_atomic_t,
pub keywords: __IncompleteArrayField<VALUE>,
}
#[repr(C)]
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index e5976a4045..fdc82c9552 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -6572,16 +6572,14 @@ impl ProfileOracle {
}
/// Map the interpreter-recorded types of the stack onto the HIR operands on our compile-time virtual stack.
- /// `stack_offset` is the number of extra stack entries above the profiled operands (e.g. 1 for
- /// sends with ARGS_BLOCKARG, where the block arg sits on top of the regular args).
- fn profile_stack(&mut self, state: &FrameState, stack_offset: usize) {
+ fn profile_stack(&mut self, state: &FrameState) {
let iseq_insn_idx = state.insn_idx;
let Some(operand_types) = self.payload.profile.get_operand_types(iseq_insn_idx) else { return };
let entry = self.types.entry(iseq_insn_idx).or_default();
// operand_types is always going to be <= stack size (otherwise it would have an underflow
// at run-time) so use that to drive iteration.
for (idx, insn_type_distribution) in operand_types.iter().rev().enumerate() {
- let insn = state.stack_topn(idx + stack_offset).expect("Unexpected stack underflow in profiling");
+ let insn = state.stack_topn(idx).expect("Unexpected stack underflow in profiling");
entry.push((insn, TypeDistributionSummary::new(insn_type_distribution)))
}
}
@@ -6784,17 +6782,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
}
}
else {
- // For sends with ARGS_BLOCKARG, the block arg sits on the stack above
- // the profiled operands (receiver + regular args). Skip it so that the
- // profile types map onto the correct HIR operands.
- let stack_offset = if opcode == YARVINSN_send || opcode == YARVINSN_opt_send_without_block {
- let cd: *const rb_call_data = get_arg(pc, 0).as_ptr();
- let flags = unsafe { vm_ci_flag(rb_get_call_data_ci(cd)) };
- usize::from(flags & VM_CALL_ARGS_BLOCKARG != 0)
- } else {
- 0
- };
- profiles.profile_stack(&exit_state, stack_offset);
+ profiles.profile_stack(&exit_state);
}
// Flag a future getlocal/setlocal to add a patch point if this instruction is not leaf.
@@ -7220,7 +7208,10 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
// Check if #new resolves to rb_class_new_instance_pass_kw.
// TODO: Guard on a profiled class and add a patch point for #new redefinition
- let argc = unsafe { vm_ci_argc((*cd).ci) } as usize;
+ let argc = crate::profile::num_arguments_on_stack(cd);
+ let ci = unsafe { get_call_data_ci(cd) };
+ let flags = unsafe { rb_vm_ci_flag(ci) };
+ assert_eq!(flags & VM_CALL_ARGS_BLOCKARG, 0);
let val = state.stack_topn(argc)?;
let test_id = fun.push_insn(block, Insn::IsMethodCfunc { val, cd, cfunc: rb_class_new_instance_pass_kw as *const u8, state: exit_id });
@@ -7646,7 +7637,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type), recompile: None });
break; // End the block
}
- let argc = unsafe { vm_ci_argc((*cd).ci) };
+ let argc = crate::profile::num_arguments_on_stack(cd);
+ assert_eq!(flags & VM_CALL_ARGS_BLOCKARG, 0);
// Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics
if unsafe { rb_zjit_iseq_tracing_currently_enabled() } {
@@ -7751,7 +7743,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type), recompile: None });
break; // End the block
}
- let argc = unsafe { vm_ci_argc((*cd).ci) };
+ let argc = crate::profile::num_arguments_on_stack(cd);
let mid = unsafe { rb_vm_ci_mid(call_info) };
// Check for calls to directives
@@ -7885,10 +7877,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing, recompile: None });
break;
}
- let argc = unsafe { vm_ci_argc((*cd).ci) };
let block_arg = (flags & VM_CALL_ARGS_BLOCKARG) != 0;
- let args = state.stack_pop_n(argc as usize + usize::from(block_arg))?;
+ let args = state.stack_pop_n(crate::profile::num_arguments_on_stack(cd))?;
let recv = state.stack_pop()?;
let block_handler = if !blockiseq.is_null() {
Some(BlockHandler::BlockIseq(blockiseq))
@@ -7985,9 +7976,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing, recompile: None });
break;
}
- let argc = unsafe { vm_ci_argc((*cd).ci) };
- let block_arg = (flags & VM_CALL_ARGS_BLOCKARG) != 0;
- let args = state.stack_pop_n(argc as usize + usize::from(block_arg))?;
+ let args = state.stack_pop_n(crate::profile::num_arguments_on_stack(cd))?;
let recv = state.stack_pop()?;
let blockiseq: IseqPtr = get_arg(pc, 1).as_ptr();
let result = fun.push_insn(block, Insn::InvokeSuper { recv, cd, blockiseq, args, state: exit_id, reason: Uncategorized(opcode) });
@@ -8079,9 +8068,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing, recompile: None });
break;
}
- let argc = unsafe { vm_ci_argc((*cd).ci) };
- let block_arg = (flags & VM_CALL_ARGS_BLOCKARG) != 0;
- let args = state.stack_pop_n(argc as usize + usize::from(block_arg))?;
+ let args = state.stack_pop_n(crate::profile::num_arguments_on_stack(cd))?;
// Check if this is a monomorphic IFUNC block handler we can specialize
let block_handler_types = profiles.payload.profile.get_operand_types(exit_state.insn_idx);
@@ -8327,7 +8314,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
}
YARVINSN_objtostring => {
let cd: *const rb_call_data = get_arg(pc, 0).as_ptr();
- let argc = unsafe { vm_ci_argc((*cd).ci) };
+ let argc = crate::profile::num_arguments_on_stack(cd);
assert_eq!(0, argc, "objtostring should not have args");
let recv = state.stack_pop()?;
diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs
index d8bcd50d96..08803c4570 100644
--- a/zjit/src/profile.rs
+++ b/zjit/src/profile.rs
@@ -95,9 +95,9 @@ fn profile_insn(bare_opcode: ruby_vminsn_type, ec: EcPtr) {
YARVINSN_invokesuper => profile_invokesuper(profiler, profile),
YARVINSN_opt_send_without_block | YARVINSN_send => {
let cd: *const rb_call_data = profiler.insn_opnd(0).as_ptr();
- let argc = unsafe { vm_ci_argc((*cd).ci) };
+ let argc = num_arguments_on_stack(cd);
// Profile all the arguments and self (+1).
- profile_operands(profiler, profile, (argc + 1) as usize);
+ profile_operands(profiler, profile, argc + 1);
}
YARVINSN_splatkw => profile_operands(profiler, profile, 2),
_ => {}
@@ -111,6 +111,15 @@ fn profile_insn(bare_opcode: ruby_vminsn_type, ec: EcPtr) {
}
}
+/// Return the argc as stated in the calldata plus:
+/// * 1 if there is an explicit blockarg, since that will be passed on the stack
+pub fn num_arguments_on_stack(cd: *const rb_call_data) -> usize {
+ let ci = unsafe { rb_get_call_data_ci(cd) };
+ let flags = unsafe { rb_vm_ci_flag(ci) };
+ let has_blockarg = (flags & VM_CALL_ARGS_BLOCKARG) != 0;
+ (unsafe { vm_ci_argc(ci) }) as usize + has_blockarg as usize
+}
+
const DISTRIBUTION_SIZE: usize = 4;
pub type TypeDistribution = Distribution<ProfiledType, DISTRIBUTION_SIZE>;
@@ -184,7 +193,7 @@ fn profile_invokesuper(profiler: &mut Profiler, profile: &mut IseqProfile) {
unsafe { rb_gc_writebarrier(profiler.iseq.into(), cme_value) };
let cd: *const rb_call_data = profiler.insn_opnd(0).as_ptr();
- let argc = unsafe { vm_ci_argc((*cd).ci) };
+ let argc = num_arguments_on_stack(cd);
// Profile all the arguments and self (+1).
profile_operands(profiler, profile, (argc + 1) as usize);