diff options
3185 files changed, 250915 insertions, 133130 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index 9ebe41e1c8..05ff204541 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -11,26 +11,29 @@ clone_depth: 10 platform: - x64 skip_commits: - message: /^\[DOC\]/ + message: /\[DOC\]/ files: - doc/* - '**/*.md' - '**/*.rdoc' + - '**/.document' + - '**/*.[1-8]' + - '**/*.ronn' environment: ruby_version: "24-%Platform%" - zlib_version: "1.2.11" matrix: + # Test only the oldest supported version because AppVeyor is unstable, its concurrency + # is limited, and compatibility issues that happen only in newer versions are rare. + # You may test some other stuff on GitHub Actions instead. - build: vs - vs: 120 - ssl: OpenSSL - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - GEMS_FOR_TEST: "" - - build: vs - vs: 140 + vs: 120 # Visual Studio 2013 ssl: OpenSSL-v111 + # The worker image name. This is NOT the Visual Studio version we're using here. APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 GEMS_FOR_TEST: "" RELINE_TEST_ENCODING: "UTF-8" +cache: + - c:\Tools\vcpkg\installed\ for: - matrix: @@ -42,6 +45,12 @@ for: - SET BITS=%Platform:x86=32% - SET BITS=%BITS:x=% - SET OPENSSL_DIR=C:\%ssl%-Win%BITS% + - cd C:\Tools\vcpkg + - git pull -q + - .\bootstrap-vcpkg.bat + - ps: Start-FileDownload 'https://github.com/microsoft/vcpkg-tool/releases/download/2023-08-09/vcpkg.exe' -FileName 'C:\Tools\vcpkg\vcpkg.exe' + - cd %APPVEYOR_BUILD_FOLDER% + - vcpkg --triplet %Platform%-windows install --x-use-aria2 libffi libyaml readline zlib - CALL SET vcvars=%%^VS%VS%COMNTOOLS^%%..\..\VC\vcvarsall.bat - SET vcvars - '"%vcvars%" %Platform:x64=amd64%' @@ -61,16 +70,20 @@ for: - mkdir \usr\local\bin - mkdir \usr\local\include - mkdir \usr\local\lib - - SET ZLIB_ZIP=.downloaded-cache\zlib%zlib_version:.=%.zip - - if not exist %ZLIB_ZIP% curl -fsSL -o %ZLIB_ZIP% --retry 10 https://zlib.net/zlib%zlib_version:.=%.zip - - 7z x -aos -o%APPVEYOR_BUILD_FOLDER%\ext\zlib %ZLIB_ZIP% - for %%I in (%OPENSSL_DIR%\*.dll) do mklink /h \usr\local\bin\%%~nxI %%I + - for %%I in (c:\Tools\vcpkg\installed\%Platform%-windows\bin\*.dll) do ( + if not %%~nI == readline mklink \usr\local\bin\%%~nxI %%I + ) - attrib +r /s /d - mkdir %Platform%-mswin_%vs% build_script: + - set HAVE_GIT=no - cd %APPVEYOR_BUILD_FOLDER% - cd %Platform%-mswin_%vs% - - ..\win32\configure.bat --without-ext=+,dbm,gdbm,readline --with-opt-dir=/usr/local --with-openssl-dir=%OPENSSL_DIR:\=/% + - >- + ..\win32\configure.bat + --with-opt-dir="/usr/local;c:/Tools/vcpkg/installed/%Platform%-windows" + --with-openssl-dir=%OPENSSL_DIR:\=/% - nmake -l - nmake install-nodoc - \usr\bin\ruby -v -e "p :locale => Encoding.find('locale'), :filesystem => Encoding.find('filesystem')" @@ -80,9 +93,26 @@ for: - set /a JOBS=%NUMBER_OF_PROCESSORS% - nmake -l "TESTOPTS=-v -q" btest - nmake -l "TESTOPTS=-v -q" test-basic - - nmake -l "TESTOPTS=-v --timeout-scale=3.0 --excludes=../test/excludes/_appveyor -j%JOBS% --exclude readline --exclude win32ole --exclude test_bignum --exclude test_syntax --exclude test_open-uri --exclude test_bundled_ca" test-all + - >- + nmake -l "TESTOPTS=--timeout-scale=3.0 + --excludes=../test/excludes/_appveyor -j%JOBS% + --exclude win32ole + --exclude test_bignum + --exclude test_syntax + --exclude test_open-uri + --exclude test_bundled_ca + " test-all # separately execute tests without -j which may crash worker with -j. - - nmake -l "TESTOPTS=-v --timeout-scale=3.0 --excludes=../test/excludes/_appveyor" test-all TESTS="../test/win32ole ../test/ruby/test_bignum.rb ../test/ruby/test_syntax.rb ../test/open-uri/test_open-uri.rb ../test/rubygems/test_bundled_ca.rb" + - >- + nmake -l + "TESTOPTS=--timeout-scale=3.0 --excludes=../test/excludes/_appveyor" + TESTS=" + ../test/win32ole + ../test/ruby/test_bignum.rb + ../test/ruby/test_syntax.rb + ../test/open-uri/test_open-uri.rb + ../test/rubygems/test_bundled_ca.rb + " test-all - nmake -l test-spec MSPECOPT=-fs # not using `-j` because sometimes `mspec -j` silently dies on Windows notifications: - provider: Webhook @@ -93,7 +123,7 @@ notifications: {{^isPullRequest}} { "ci": "AppVeyor CI", - "env": "Visual Studio 2013 / 2015", + "env": "Visual Studio 2013", "url": "{{buildUrl}}", "commit": "{{commitId}}", "branch": "{{branch}}" diff --git a/.cirrus.yml b/.cirrus.yml deleted file mode 100644 index c8fb326c89..0000000000 --- a/.cirrus.yml +++ /dev/null @@ -1,64 +0,0 @@ -# This CI is used to test Arm cases. We can set the maximum 16 tasks. -# The entire testing design is inspired from .github/workflows/compilers.yml. - -# By default, Cirrus mounts an empty volume to `/tmp` -# which triggers all sorts of warnings like "system temporary path is world-writable: /tmp". -# Lets workaround it by specifying a custom volume mount point. -env: - CIRRUS_VOLUME: /cirrus-ci-volume - LANG: C.UTF-8 - -task: - name: Arm64 Graviton2 / $CC - skip: "changesIncludeOnly('doc/**', '**.{md,rdoc}')" - arm_container: - # We use the arm64 images at http://ghcr.io/ruby/ruby-ci-image . - image: ghcr.io/ruby/ruby-ci-image:$CC - # Define the used cpu core in each matrix task. We can use total 16 cpu - # cores in entire matrix. [cpu] = [total cpu: 16] / [number of tasks] - cpu: 8 - # We can request maximum 4 GB per cpu. - # [memory per task] = [memory per cpu: 4 GB] * [cpu] - memory: 32G - env: - CIRRUS_CLONE_DEPTH: 50 - optflags: '-O1' - debugflags: '-ggdb3' - RUBY_PREFIX: /tmp/ruby-prefix - RUBY_DEBUG: ci rgengc - RUBY_TESTOPTS: >- - -q - --color=always - --tty=no - matrix: - CC: clang-12 - CC: gcc-11 - id_script: id - set_env_script: - # Set `GNUMAKEFLAGS`, because the flags are GNU make specific. Note using - # the `make` environment variable used in compilers.yml causes some rubygems - # tests to fail. - # https://github.com/rubygems/rubygems/issues/4921 - - echo "GNUMAKEFLAGS=-s -j$((1 + $CIRRUS_CPU))" >> $CIRRUS_ENV - print_env_script: - - echo "GNUMAKEFLAGS=$GNUMAKEFLAGS" - # Arm containers are executed in AWS's EKS, and it's not yet supporting IPv6 - # See https://github.com/aws/containers-roadmap/issues/835 - disable_ipv6_script: sudo ./tool/disable_ipv6.sh - autogen_script: ./autogen.sh - configure_script: >- - ./configure -C - --enable-debug-env - --disable-install-doc - --with-ext=-test-/cxxanyargs,+ - --prefix="$RUBY_PREFIX" - make_extract-extlibs_script: make extract-extlibs - make_incs_script: make incs - make_script: make - make_leaked-globals_script: make leaked-globals - make_test_script: make test - make_install_script: make install - install_gems_for_test_script: $RUBY_PREFIX/bin/gem install --no-doc timezone tzinfo - make_test-tool_script: make test-tool - make_test-all_script: make test-all - make_test-spec_script: make test-spec @@ -18,13 +18,18 @@ gc.rb io.rb kernel.rb marshal.rb +mjit.rb numeric.rb nilclass.rb pack.rb ractor.rb +string.rb +symbol.rb timev.rb +thread_sync.rb trace_point.rb warning.rb +yjit.rb # the lib/ directory (which has its own .document file) lib @@ -40,7 +45,6 @@ README.ja.md COPYING COPYING.ja -CONTRIBUTING.md LEGAL @@ -544,13 +544,13 @@ end define rp_class printf "(struct RClass *) %p", (void*)$arg0 - if ((struct RClass *)($arg0))->ptr.origin_ != $arg0 - printf " -> %p", ((struct RClass *)($arg0))->ptr.origin_ + if RCLASS_ORIGIN((struct RClass *)($arg0)) != $arg0 + printf " -> %p", RCLASS_ORIGIN((struct RClass *)($arg0)) end printf "\n" rb_classname $arg0 print/x *(struct RClass *)($arg0) - print *((struct RClass *)($arg0))->ptr + print *RCLASS_EXT((struct RClass *)($arg0)) end document rp_class Print the content of a Class/Module. @@ -979,8 +979,8 @@ end define rb_ps_vm print $ps_vm = (rb_vm_t*)$arg0 - set $ps_thread_ln = $ps_vm->living_threads.n.next - set $ps_thread_ln_last = $ps_vm->living_threads.n.prev + set $ps_thread_ln = $ps_vm->ractor.main_ractor.threads.set.n.next + set $ps_thread_ln_last = $ps_vm->ractor.main_ractor.threads.set.n.prev while 1 set $ps_thread_th = (rb_thread_t *)$ps_thread_ln set $ps_thread = (VALUE)($ps_thread_th->self) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..6c5eac5a0f --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,23 @@ +# This is a file used by GitHub to ignore the following commits on `git blame`. +# +# You can also do the same thing in your local repository with: +# $ git config --local blame.ignoreRevsFile .git-blame-ignore-revs + +# Expand tabs +5b21e94bebed90180d8ff63dad03b8b948361089 + +# Enable Style/StringLiterals cop for RubyGems/Bundler +d7ffd3fea402239b16833cc434404a7af82d44f3 + +# [ruby/digest] Revert tab-expansion in external files +48b09aae7ec5632209229dcc294dd0d75a93a17f +8a65cf3b61c60e4cb886f59a73ff6db44364bfa9 +39dc9f9093901d40d2998653948d5da38b18ee2c + +# [ruby/io-nonblock] Revert tab expansion +f28287d34c03f472ffe90ea262bdde9affd4b965 +0d842fecb4f75ab3b1d4097ebdb8e88f51558041 +4ba2c66761d6a293abdfba409241d31063cefd62 + +# Make benchmark indentation consistent +fc4acf8cae82e5196186d3278d831f2438479d91 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 15abc79af6..0000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,10 +0,0 @@ -# Lines starting with '#' are comments. -# Each line is a file pattern followed by one or more owners. -# Code owners will be automatically tagged as reviewers when a pull request is opened - -# YJIT sources and tests -yjit* @maximecb @xrxr @tenderlove -doc/yjit/* @maximecb @xrxr @tenderlove -bootstraptest/test_yjit* @maximecb @xrxr @tenderlove -test/ruby/test_yjit* @maximecb @xrxr @tenderlove -.github/workflows/yjit* @maximecb @xrxr @tenderlove diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..bc63aca35b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'monthly' diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index cf86c2d7d7..ebaafe3bf0 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -4,23 +4,37 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' + - '**/.document' pull_request: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} +permissions: + contents: read + jobs: baseruby: name: BASERUBY - runs-on: ubuntu-20.04 - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + runs-on: ubuntu-22.04 + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} strategy: matrix: ruby: @@ -29,28 +43,29 @@ jobs: # - ruby-2.4 # - ruby-2.5 # - ruby-2.6 - - ruby-2.7 +# - ruby-2.7 - ruby-3.0 + - ruby-3.1 steps: - - uses: actions/checkout@v2 - - uses: actions/cache@v2 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: .downloaded-cache key: downloaded-cache - - uses: ruby/setup-ruby@v1 + - uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 with: ruby-version: ${{ matrix.ruby }} bundler: none - run: echo "GNUMAKEFLAGS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV - - run: sudo apt-get install build-essential autoconf bison + - run: sudo apt-get install build-essential autoconf bison libyaml-dev - run: ./autogen.sh - run: ./configure --disable-install-doc - run: make common-srcs - run: make incs - run: make all - run: make test - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { @@ -58,7 +73,7 @@ jobs: "env": "${{ github.workflow }} / BASERUBY @ ${{ matrix.ruby }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index 2a8e6c244c..070c0fa1dd 100644 --- a/.github/workflows/bundled_gems.yml +++ b/.github/workflows/bundled_gems.yml @@ -2,18 +2,31 @@ name: bundled_gems on: push: + branches: [ "master" ] paths: - '.github/workflows/bundled_gems.yml' - 'gems/bundled_gems' pull_request: + branches: [ "master" ] + paths: + - '.github/workflows/bundled_gems.yml' + - 'gems/bundled_gems' + merge_group: + branches: [ "master" ] paths: - '.github/workflows/bundled_gems.yml' - 'gems/bundled_gems' schedule: - cron: '45 6 * * *' + workflow_dispatch: + +permissions: # added using https://github.com/step-security/secure-workflows + contents: read jobs: update: + permissions: + contents: write # for Git to git push if: ${{ github.event_name != 'schedule' || github.repository == 'ruby/ruby' }} name: update ${{ github.workflow }} runs-on: ubuntu-latest @@ -28,9 +41,9 @@ jobs: echo "GNUMAKEFLAGS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV echo "TODAY=$(date +%F)" >> $GITHUB_ENV - - uses: actions/checkout@v2 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/cache@v2 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: .downloaded-cache key: downloaded-cache-${{ github.sha }} @@ -50,6 +63,7 @@ jobs: - name: Maintain updated gems list in NEWS run: | + #!ruby require 'json' news = File.read("NEWS.md") prev = news[/since the \*+(\d+\.\d+\.\d+)\*+/, 1] @@ -67,14 +81,14 @@ jobs: [g, v] unless last[g] == v end changed, added = changed.partition {|g, _| last[g]} - news.sub!(/^\*( +)The following #{type} gems? are updated\.\n\K(?: \1\* .*\n)*/) do - mark = "#{$1} * " + news.sub!(/^\*( +)The following #{type} gems? are updated\.\n+\K(?: \1\*( +).*\n)*/) do + mark = "#{$1} *#{$2}" changed.map {|g, v|"#{mark}#{g} #{v}\n"}.join("") end or next - news.sub!(/^\*( +)The following default gems are now bundled gems\.\n\K(?: \1\* .*\n)*/) do - mark = "#{$1} * " + news.sub!(/^\*( +)The following default gems are now bundled gems\.\n+\K(?: \1\*( +).*\n)*/) do + mark = "#{$1} *#{$2}" added.map {|g, v|"#{mark}#{g} #{v}\n"}.join("") - end or next if added + end or next unless added.empty? File.write("NEWS.md", news) end shell: ruby {0} @@ -100,6 +114,11 @@ jobs: make if: ${{ steps.diff.outcome == 'failure' }} + - name: Prepare bundled gems + run: | + make -s prepare-gems + if: ${{ steps.diff.outcome == 'failure' }} + - name: Test bundled gems run: | make -s test-bundled-gems @@ -131,3 +150,17 @@ jobs: GIT_AUTHOR_NAME: git GIT_COMMITTER_NAME: git if: ${{ github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull') && steps.show.outcome == 'failure' }} + + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 + with: + payload: | + { + "ci": "GitHub Actions", + "env": "${{ github.workflow }} / update", + "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 }} # ruby-lang slack: ruby/simpler-alerts-bot + if: ${{ failure() && github.event_name == 'push' }} diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index 6c7e8e5787..79b2916feb 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -3,26 +3,40 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' + - '**/.document' pull_request: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} +permissions: + contents: read + jobs: update-deps: strategy: matrix: - os: [ubuntu-20.04] + os: [ubuntu-22.04] fail-fast: true runs-on: ${{ matrix.os }} - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} steps: - name: Install libraries run: | @@ -32,15 +46,14 @@ jobs: if: ${{ contains(matrix.os, 'ubuntu') }} - name: Install libraries run: | - brew upgrade brew install gmp libffi openssl@1.1 zlib autoconf automake libtool readline if: ${{ contains(matrix.os, 'macos') }} - name: git config run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@v2 - - uses: actions/cache@v2 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: .downloaded-cache key: downloaded-cache @@ -50,7 +63,7 @@ jobs: - run: make all golf - run: ruby tool/update-deps --fix - run: git diff --no-ext-diff --ignore-submodules --exit-code - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { @@ -58,7 +71,7 @@ jobs: "env": "${{ matrix.os }} / Dependencies need to update", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml deleted file mode 100644 index 2872c96ffd..0000000000 --- a/.github/workflows/check_misc.yml +++ /dev/null @@ -1,97 +0,0 @@ -name: Miscellaneous checks -on: [push, pull_request] - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -jobs: - checks: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Check if C-sources are US-ASCII - run: | - ! grep -r -n '[^ -~]' *.[chy] include internal win32/*.[ch] - - name: Check for trailing spaces - run: | - ! git grep -n '[ ]$' '*.rb' '*.[chy]' - - name: Check for header macros - run: | - ! for header in ruby/*.h; do \ - git grep -l -F -e $header -e HAVE_`echo $header | tr a-z./ A-Z__` -- . > /dev/null || echo $header - done | grep -F . - working-directory: include - - - uses: actions/cache@v2 - with: - path: .downloaded-cache - key: downloaded-cache-${{ github.sha }} - restore-keys: | - downloaded-cache - - - name: Download previous gems list - run: | - data=default_gems.json - mkdir -p .downloaded-cache - ln -s .downloaded-cache/$data . - curl -O -R -z ./$data https://stdgems.org/$data - - - name: Make default gems list - run: | - require 'rubygems' - $:.unshift "lib" - rgver = File.foreach("lib/rubygems.rb") do |line| - break $1 if /^\s*VERSION\s*=\s*"([^"]+)"/ =~ line - end - gems = Dir.glob("{ext,lib}/**/*.gemspec").map do |f| - spec = Gem::Specification.load(f) - "#{spec.name} #{spec.version}" - end.sort - File.open("gems/default_gems", "w") do |f| - f.puts "RubyGems #{rgver}" - f.puts gems - end - shell: ruby --disable=gems {0} - - - name: Maintain updated gems list in NEWS - run: | - require 'json' - news = File.read("NEWS.md") - prev = news[/since the \*+(\d+\.\d+\.\d+)\*+/, 1] - prevs = [prev, prev.sub(/\.\d+\z/, '')] - %W[default].each do |type| - last = JSON.parse(File.read("#{type}_gems.json"))['gems'].filter_map do |g| - v = g['versions'].values_at(*prevs).compact.first - g = g['gem'] - g = 'RubyGems' if g == 'rubygems' - [g, v] if v - end.to_h - changed = File.foreach("gems/#{type}_gems").filter_map do |l| - next if l.start_with?("#") - g, v = l.split(" ", 3) - [g, v] unless last[g] == v - end - news.sub!(/^\*( +)The following #{type} gems? are updated\.\n\K(?: \1\* .*\n)*/) do - mark = "#{$1} * " - changed.map {|g, v|"#{mark}#{g} #{v}\n"}.join("") - end or next - File.write("NEWS.md", news) - end - shell: ruby {0} - - - name: Check diffs - id: diff - run: | - git diff --color --no-ext-diff --ignore-submodules --exit-code NEWS.md - continue-on-error: true - - name: Commit - run: | - git pull --ff-only origin ${GITHUB_REF#refs/heads/} - git commit --message="Update default gems list at ${GITHUB_SHA:0:30} [ci skip]" NEWS.md - git push origin ${GITHUB_REF#refs/heads/} - env: - EMAIL: svn-admin@ruby-lang.org - GIT_AUTHOR_NAME: git - GIT_COMMITTER_NAME: git - if: ${{ github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull') && steps.diff.outcome == 'failure' }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f81c79902d..8dba76fbe2 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,29 +1,42 @@ name: "Code scanning - action" on: - push: - paths-ignore: - - 'doc/**' - - '**.md' - - '**.rdoc' - pull_request: - paths-ignore: - - 'doc/**' - - '**.md' - - '**.rdoc' + # push: + # paths-ignore: + # - 'doc/**' + # - '**/man' + # - '**.md' + # - '**.rdoc' + # - '**/.document' + # pull_request: + # paths-ignore: + # - 'doc/**' + # - '**/man' + # - '**.md' + # - '**.rdoc' + # - '**/.document' schedule: - - cron: '0 12 * * 4' + - cron: '0 12 * * *' + workflow_dispatch: concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} +permissions: # added using https://github.com/step-security/secure-workflows + contents: read + jobs: CodeQL-Build: # CodeQL runs on ubuntu-latest and windows-latest + permissions: + actions: read # for github/codeql-action/init to get workflow details + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/autobuild to send a status report runs-on: ubuntu-latest - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + # CodeQL fails to run pull requests from dependabot due to missing write access to upload results. + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') && github.event.head_commit.pusher.name != 'dependabot[bot]' }} env: enable_install_doc: no @@ -36,9 +49,9 @@ jobs: 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 ruby - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/cache@v2 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: .downloaded-cache key: downloaded-cache @@ -47,15 +60,16 @@ jobs: run: sudo rm /usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2.1.37 with: config-file: ./.github/codeql/codeql-config.yml + trap-caching: false - name: Set ENV run: echo "GNUMAKEFLAGS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2.1.37 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2.1.37 diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index f3a872de8f..caf12cc0f4 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -4,28 +4,36 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' + - '**/.document' pull_request: paths-ignore: - 'doc/**' - - '**.md' + - '**/man' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' - '**.rdoc' + - '**/.document' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} -# Github actions does not support YAML anchors. This creative use of +# GitHub actions does not support YAML anchors. This creative use of # environment variables (plus the "echo $GITHUB_ENV" hack) is to reroute that # restriction. env: - default_cc: clang-14 + default_cc: clang-15 append_cc: '' - crosshost: '' # -O1 is faster than -O3 in our tests... Majority of time are consumed trying - # to optimize binaries. Also Github Actions run on relatively modern CPUs + # to optimize binaries. Also GitHub Actions run on relatively modern CPUs # compared to, say, GCC 4 or Clang 3. We don't specify `-march=native` # because compilers tend not understand what the CPU is. optflags: '-O1' @@ -55,142 +63,160 @@ env: --color=always --tty=no +permissions: + contents: read + jobs: compile: strategy: fail-fast: false matrix: + env: + - {} entry: - - { key: default_cc, name: gcc-11, value: gcc-11, container: gcc-11 } - - { key: default_cc, name: gcc-10, value: gcc-10, container: gcc-10 } - - { key: default_cc, name: gcc-9, value: gcc-9, container: gcc-9 } - - { key: default_cc, name: gcc-8, value: gcc-8, container: gcc-8 } - - { key: default_cc, name: gcc-7, value: gcc-7, container: gcc-7 } - - { key: default_cc, name: gcc-6, value: gcc-6, container: gcc-6 } - - { key: default_cc, name: gcc-5, value: gcc-5, container: gcc-5 } - - { key: default_cc, name: gcc-4.8, value: gcc-4.8, container: gcc-4.8 } - - key: default_cc - name: 'gcc-11 LTO' - value: 'gcc-11 -O2 -flto=auto -ffat-lto-objects' - container: gcc-11 - shared: '--disable-shared' + - { name: gcc-12, env: { default_cc: gcc-12 } } + - { name: gcc-11, env: { default_cc: gcc-11 } } + - { name: gcc-10, env: { default_cc: gcc-10 } } + - { name: gcc-9, env: { default_cc: gcc-9 } } + - { name: gcc-8, env: { default_cc: gcc-8 } } + - { name: gcc-7, env: { default_cc: gcc-7 } } + - name: 'gcc-13 LTO' + container: gcc-13 + env: + default_cc: 'gcc-13 -flto=auto -ffat-lto-objects -Werror=lto-type-mismatch' + optflags: '-O2' + shared: disable # check: true - - { key: default_cc, name: clang-14, value: clang-14, container: clang-14 } - - { key: default_cc, name: clang-13, value: clang-13, container: clang-13 } - - { key: default_cc, name: clang-12, value: clang-12, container: clang-12 } - - { key: default_cc, name: clang-11, value: clang-11, container: clang-11 } - - { key: default_cc, name: clang-10, value: clang-10, container: clang-10 } - - { key: default_cc, name: clang-9, value: clang-9, container: clang-9 } - - { key: default_cc, name: clang-8, value: clang-8, container: clang-8 } - - { key: default_cc, name: clang-7, value: clang-7, container: clang-7 } - - { key: default_cc, name: clang-6.0, value: clang-6.0, container: clang-6.0 } - - { key: default_cc, name: clang-5.0, value: clang-5.0, container: clang-5.0 } - - { key: default_cc, name: clang-4.0, value: clang-4.0, container: clang-4.0 } - - { key: default_cc, name: clang-3.9, value: clang-3.9, container: clang-3.9 } - - key: default_cc - name: 'clang-14 LTO' - value: 'clang-14 -O2 -flto=auto' - container: clang-14 - shared: '--disable-shared' + - { name: clang-16, env: { default_cc: clang-16 } } + - { name: clang-15, env: { default_cc: clang-15 } } + - { name: clang-14, env: { default_cc: clang-14 } } + - { name: clang-13, env: { default_cc: clang-13 } } + - { name: clang-12, env: { default_cc: clang-12 } } + - { name: clang-11, env: { default_cc: clang-11 } } + - { name: clang-10, env: { default_cc: clang-10 } } + # llvm-objcopy<=9 doesn't have --wildcard. It compiles, but leaves Rust symbols in libyjit.o. + - { name: clang-9, env: { default_cc: clang-9, append_configure: '--disable-yjit' } } + - { name: clang-8, env: { default_cc: clang-8, append_configure: '--disable-yjit' } } + - { name: clang-7, env: { default_cc: clang-7, append_configure: '--disable-yjit' } } + - { name: clang-6.0, env: { default_cc: clang-6.0, append_configure: '--disable-yjit' } } + - name: 'clang-16 LTO' + container: clang-16 + env: + default_cc: 'clang-16 -flto=auto' + optflags: '-O2' + shared: disable # check: true - - { key: crosshost, name: aarch64-linux-gnu, value: aarch64-linux-gnu, container: crossbuild-essential-arm64 } -# - { key: crosshost, name: arm-linux-gnueabi, value: arm-linux-gnueabi } -# - { key: crosshost, name: arm-linux-gnueabihf, value: arm-linux-gnueabihf } -# - { key: crosshost, name: i686-w64-mingw32, value: i686-w64-mingw32 } -# - { key: crosshost, name: powerpc-linux-gnu, value: powerpc-linux-gnu } - - { key: crosshost, name: powerpc64le-linux-gnu, value: powerpc64le-linux-gnu, container: crossbuild-essential-ppc64el } - - { key: crosshost, name: s390x-linux-gnu, value: s390x-linux-gnu, container: crossbuild-essential-s390x } - - { key: crosshost, name: x86_64-w64-mingw32, value: x86_64-w64-mingw32, container: mingw-w64 } - - - { key: append_cc, name: c99, value: '-std=c99 -Werror=pedantic -pedantic-errors' } -# - { key: append_cc, name: c11, value: '-std=c11 -Werror=pedantic -pedantic-errors' } -# - { key: append_cc, name: c17, value: '-std=c17 -Werror=pedantic -pedantic-errors' } - - { key: append_cc, name: c2x, value: '-std=c2x -Werror=pedantic -pedantic-errors' } - - { key: CXXFLAGS, name: c++98, value: '-std=c++98 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } -# - { key: CXXFLAGS, name: c++11, value: '-std=c++11 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } -# - { key: CXXFLAGS, name: c++14, value: '-std=c++14 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } -# - { key: CXXFLAGS, name: c++17, value: '-std=c++17 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } - - { key: CXXFLAGS, name: c++2a, value: '-std=c++2a -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } - - - { key: optflags, name: '-O0', value: '-O0 -march=x86-64 -mtune=generic' } -# - { key: optflags, name: '-O3', value: '-O3 -march=x86-64 -mtune=generic', check: true } - - - { key: append_configure, name: gmp, value: '--with-gmp' } - - { key: append_configure, name: jemalloc, value: '--with-jemalloc' } - - { key: append_configure, name: valgrind, value: '--with-valgrind' } - - { key: append_configure, name: 'coroutine=ucontext', value: '--with-coroutine=ucontext' } - - { key: append_configure, name: 'coroutine=pthread', value: '--with-coroutine=pthread' } - - { key: append_configure, name: disable-jit-support, value: '--disable-jit-support' } - - { key: append_configure, name: disable-dln, value: '--disable-dln' } - - { key: append_configure, name: disable-rubygems, value: '--disable-rubygems' } - - - { key: cppflags, name: OPT_THREADED_CODE=1, value: '-DOPT_THREADED_CODE=1' } - - { key: cppflags, name: OPT_THREADED_CODE=2, value: '-DOPT_THREADED_CODE=2' } - - { key: cppflags, name: OPT_THREADED_CODE=3, value: '-DOPT_THREADED_CODE=3' } - - - { key: cppflags, name: NDEBUG, value: '-DNDEBUG' } - - { key: cppflags, name: RUBY_DEBUG, value: '-DRUBY_DEBUG' } -# - { key: cppflags, name: ARRAY_DEBUG, value: '-DARRAY_DEBUG' } -# - { key: cppflags, name: BIGNUM_DEBUG, value: '-DBIGNUM_DEBUG' } -# - { key: cppflags, name: CCAN_LIST_DEBUG, value: '-DCCAN_LIST_DEBUG' } -# - { key: cppflags, name: CPDEBUG=-1, value: '-DCPDEBUG=-1' } -# - { key: cppflags, name: ENC_DEBUG, value: '-DENC_DEBUG' } -# - { key: cppflags, name: GC_DEBUG, value: '-DGC_DEBUG' } -# - { key: cppflags, name: HASH_DEBUG, value: '-DHASH_DEBUG' } -# - { key: cppflags, name: ID_TABLE_DEBUG, value: '-DID_TABLE_DEBUG' } -# - { key: cppflags, name: RGENGC_DEBUG=-1, value: '-DRGENGC_DEBUG=-1' } -# - { key: cppflags, name: SYMBOL_DEBUG, value: '-DSYMBOL_DEBUG' } -# - { key: cppflags, name: THREAD_DEBUG=-1, value: '-DTHREAD_DEBUG=-1' } - -# - { key: cppflags, name: RGENGC_CHECK_MODE, value: '-DRGENGC_CHECK_MODE' } -# - { key: cppflags, name: TRANSIENT_HEAP_CHECK_MODE, value: '-DTRANSIENT_HEAP_CHECK_MODE' } -# - { key: cppflags, name: VM_CHECK_MODE, value: '-DVM_CHECK_MODE' } - - - { key: cppflags, name: USE_EMBED_CI=0, value: '-DUSE_EMBED_CI=0' } - - { key: cppflags, name: USE_FLONUM=0, value: '-DUSE_FLONUM=0' } -# - { key: cppflags, name: USE_GC_MALLOC_OBJ_INFO_DETAILS, value: '-DUSE_GC_MALLOC_OBJ_INFO_DETAILS' } - - { key: cppflags, name: USE_LAZY_LOAD, value: '-DUSE_LAZY_LOAD' } -# - { key: cppflags, name: USE_RINCGC=0, value: '-DUSE_RINCGC=0' } -# - { key: cppflags, name: USE_SYMBOL_GC=0, value: '-DUSE_SYMBOL_GC=0' } -# - { key: cppflags, name: USE_THREAD_CACHE=0, value: '-DUSE_THREAD_CACHE=0' } -# - { key: cppflags, name: USE_TRANSIENT_HEAP=0, value: '-DUSE_TRANSIENT_HEAP=0' } -# - { key: cppflags, name: USE_RUBY_DEBUG_LOG=1, value: '-DUSE_RUBY_DEBUG_LOG=1' } - - { key: cppflags, name: USE_RVARGC=0, value: '-DUSE_RVARGC=0' } -# - { key: cppflags, name: USE_RVARGC=1, value: '-DUSE_RVARGC=1' } - - - { key: cppflags, name: DEBUG_FIND_TIME_NUMGUESS, value: '-DDEBUG_FIND_TIME_NUMGUESS' } - - { key: cppflags, name: DEBUG_INTEGER_PACK, value: '-DDEBUG_INTEGER_PACK' } -# - { key: cppflags, name: ENABLE_PATH_CHECK, value: '-DENABLE_PATH_CHECK' } - - - { key: cppflags, name: GC_DEBUG_STRESS_TO_CLASS, value: '-DGC_DEBUG_STRESS_TO_CLASS' } -# - { key: cppflags, name: GC_ENABLE_LAZY_SWEEP=0, value: '-DGC_ENABLE_LAZY_SWEEP=0' } -# - { key: cppflags, name: GC_PROFILE_DETAIL_MEMOTY, value: '-DGC_PROFILE_DETAIL_MEMOTY' } -# - { key: cppflags, name: GC_PROFILE_MORE_DETAIL, value: '-DGC_PROFILE_MORE_DETAIL' } - -# - { key: cppflags, name: CALC_EXACT_MALLOC_SIZE, value: '-DCALC_EXACT_MALLOC_SIZE' } -# - { key: cppflags, name: MALLOC_ALLOCATED_SIZE_CHECK, value: '-DMALLOC_ALLOCATED_SIZE_CHECK' } - -# - { key: cppflags, name: IBF_ISEQ_ENABLE_LOCAL_BUFFER, value: '-DIBF_ISEQ_ENABLE_LOCAL_BUFFER' } - -# - { key: cppflags, name: RGENGC_ESTIMATE_OLDMALLOC, value: '-DRGENGC_ESTIMATE_OLDMALLOC' } -# - { key: cppflags, name: RGENGC_FORCE_MAJOR_GC, value: '-DRGENGC_FORCE_MAJOR_GC' } -# - { key: cppflags, name: RGENGC_OBJ_INFO, value: '-DRGENGC_OBJ_INFO' } -# - { key: cppflags, name: RGENGC_OLD_NEWOBJ_CHECK, value: '-DRGENGC_OLD_NEWOBJ_CHECK' } -# - { key: cppflags, name: RGENGC_PROFILE, value: '-DRGENGC_PROFILE' } - -# - { key: cppflags, name: VM_DEBUG_BP_CHECK, value: '-DVM_DEBUG_BP_CHECK' } -# - { key: cppflags, name: VM_DEBUG_VERIFY_METHOD_CACHE, value: '-DVM_DEBUG_VERIFY_METHOD_CACHE' } - - - { key: cppflags, name: MJIT_FORCE_ENABLE, value: '-DMJIT_FORCE_ENABLE' } - - { key: cppflags, name: YJIT_FORCE_ENABLE, value: '-DYJIT_FORCE_ENABLE' } +# - { name: aarch64-linux-gnu, crosshost: aarch64-linux-gnu, container: crossbuild-essential-arm64 } +# - { name: arm-linux-gnueabi, crosshost: arm-linux-gnueabi } +# - { name: arm-linux-gnueabihf, crosshost: arm-linux-gnueabihf } +# - { name: i686-w64-mingw32, crosshost: i686-w64-mingw32 } +# - { name: powerpc-linux-gnu, crosshost: powerpc-linux-gnu } +# - { name: powerpc64le-linux-gnu, crosshost: powerpc64le-linux-gnu, container: crossbuild-essential-ppc64el } +# - { name: s390x-linux-gnu, crosshost: s390x-linux-gnu, container: crossbuild-essential-s390x } +# - { name: x86_64-w64-mingw32, crosshost: x86_64-w64-mingw32, container: mingw-w64 } + + # -Wno-strict-prototypes is necessary with current clang-15 since + # older autoconf generate functions without prototype and -pedantic + # now implies strict-prototypes. Disabling the error but leaving the + # warning generates a lot of noise from use of ANYARGS in + # rb_define_method() and friends. + # See: https://github.com/llvm/llvm-project/commit/11da1b53d8cd3507959022cd790d5a7ad4573d94 + - { name: c99, env: { append_cc: '-std=c99 -Werror=pedantic -pedantic-errors -Wno-strict-prototypes' } } +# - { name: c11, env: { append_cc: '-std=c11 -Werror=pedantic -pedantic-errors -Wno-strict-prototypes' } } +# - { name: c17, env: { append_cc: '-std=c17 -Werror=pedantic -pedantic-errors -Wno-strict-prototypes' } } + - { name: c2x, env: { append_cc: '-std=c2x -Werror=pedantic -pedantic-errors -Wno-strict-prototypes' } } + - { name: c++98, env: { CXXFLAGS: '-std=c++98 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } +# - { name: c++11, env: { CXXFLAGS: '-std=c++11 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } +# - { name: c++14, env: { CXXFLAGS: '-std=c++14 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } +# - { name: c++17, env: { CXXFLAGS: '-std=c++17 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } + - { name: c++2a, env: { CXXFLAGS: '-std=c++2a -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } + + - { name: '-O0', env: { optflags: '-O0 -march=x86-64 -mtune=generic' } } +# - { name: '-O3', env: { optflags: '-O3 -march=x86-64 -mtune=generic' }, check: true } + + - { name: gmp, env: { append_configure: '--with-gmp' } } + - { name: jemalloc, env: { append_configure: '--with-jemalloc' } } + - { name: valgrind, env: { append_configure: '--with-valgrind' } } + - { name: 'coroutine=ucontext', env: { append_configure: '--with-coroutine=ucontext' } } + - { name: 'coroutine=pthread', env: { append_configure: '--with-coroutine=pthread' } } + - { name: disable-jit-support, env: { append_configure: '--disable-jit-support' } } + - { name: disable-dln, env: { append_configure: '--disable-dln' } } + - { name: enable-mkmf-verbose, env: { append_configure: '--enable-mkmf-verbose' } } + - { name: disable-rubygems, env: { append_configure: '--disable-rubygems' } } + - { name: RUBY_DEVEL, env: { append_configure: '--enable-devel' } } + + - { name: OPT_THREADED_CODE=1, env: { cppflags: '-DOPT_THREADED_CODE=1' } } + - { name: OPT_THREADED_CODE=2, env: { cppflags: '-DOPT_THREADED_CODE=2' } } + - { name: OPT_THREADED_CODE=3, env: { cppflags: '-DOPT_THREADED_CODE=3' } } + + - { name: NDEBUG, env: { cppflags: '-DNDEBUG' } } + - { name: RUBY_DEBUG, env: { cppflags: '-DRUBY_DEBUG' } } +# - { name: ARRAY_DEBUG, env: { cppflags: '-DARRAY_DEBUG' } } +# - { name: BIGNUM_DEBUG, env: { cppflags: '-DBIGNUM_DEBUG' } } +# - { name: CCAN_LIST_DEBUG, env: { cppflags: '-DCCAN_LIST_DEBUG' } } +# - { name: CPDEBUG=-1, env: { cppflags: '-DCPDEBUG=-1' } } +# - { name: ENC_DEBUG, env: { cppflags: '-DENC_DEBUG' } } +# - { name: GC_DEBUG, env: { cppflags: '-DGC_DEBUG' } } +# - { name: HASH_DEBUG, env: { cppflags: '-DHASH_DEBUG' } } +# - { name: ID_TABLE_DEBUG, env: { cppflags: '-DID_TABLE_DEBUG' } } +# - { name: RGENGC_DEBUG=-1, env: { cppflags: '-DRGENGC_DEBUG=-1' } } +# - { name: SYMBOL_DEBUG, env: { cppflags: '-DSYMBOL_DEBUG' } } + +# - { name: RGENGC_CHECK_MODE, env: { cppflags: '-DRGENGC_CHECK_MODE' } } +# - { name: TRANSIENT_HEAP_CHECK_MODE, env: { cppflags: '-DTRANSIENT_HEAP_CHECK_MODE' } } +# - { name: VM_CHECK_MODE, env: { cppflags: '-DVM_CHECK_MODE' } } + + - { name: USE_EMBED_CI=0, env: { cppflags: '-DUSE_EMBED_CI=0' } } + - name: USE_FLONUM=0, + env: + cppflags: '-DUSE_FLONUM=0' + # yjit requires FLONUM for the pointer tagging scheme + append_configure: '--disable-yjit' +# - { name: USE_GC_MALLOC_OBJ_INFO_DETAILS, env: { cppflags: '-DUSE_GC_MALLOC_OBJ_INFO_DETAILS' } } + - { name: USE_LAZY_LOAD, env: { cppflags: '-DUSE_LAZY_LOAD' } } +# - { name: USE_RINCGC=0, env: { cppflags: '-DUSE_RINCGC=0' } } +# - { name: USE_SYMBOL_GC=0, env: { cppflags: '-DUSE_SYMBOL_GC=0' } } +# - { name: USE_THREAD_CACHE=0, env: { cppflags: '-DUSE_THREAD_CACHE=0' } } +# - { name: USE_TRANSIENT_HEAP=0, env: { cppflags: '-DUSE_TRANSIENT_HEAP=0' } } +# - { name: USE_RUBY_DEBUG_LOG=1, env: { cppflags: '-DUSE_RUBY_DEBUG_LOG=1' } } + - { name: USE_RVARGC=0, env: { cppflags: '-DUSE_RVARGC=0' } } +# - { name: USE_RVARGC=1, env: { cppflags: '-DUSE_RVARGC=1' } } +# - { name: USE_DEBUG_COUNTER, env: { cppflags: '-DUSE_DEBUG_COUNTER=1', RUBY_DEBUG_COUNTER_DISABLE: '1' } } + + - { name: DEBUG_FIND_TIME_NUMGUESS, env: { cppflags: '-DDEBUG_FIND_TIME_NUMGUESS' } } + - { name: DEBUG_INTEGER_PACK, env: { cppflags: '-DDEBUG_INTEGER_PACK' } } +# - { name: ENABLE_PATH_CHECK, env: { cppflags: '-DENABLE_PATH_CHECK' } } + + - { name: GC_DEBUG_STRESS_TO_CLASS, env: { cppflags: '-DGC_DEBUG_STRESS_TO_CLASS' } } +# - { name: GC_ENABLE_LAZY_SWEEP=0, env: { cppflags: '-DGC_ENABLE_LAZY_SWEEP=0' } } +# - { name: GC_PROFILE_DETAIL_MEMOTY, env: { cppflags: '-DGC_PROFILE_DETAIL_MEMOTY' } } +# - { name: GC_PROFILE_MORE_DETAIL, env: { cppflags: '-DGC_PROFILE_MORE_DETAIL' } } + +# - { name: CALC_EXACT_MALLOC_SIZE, env: { cppflags: '-DCALC_EXACT_MALLOC_SIZE' } } +# - { name: MALLOC_ALLOCATED_SIZE_CHECK, env: { cppflags: '-DMALLOC_ALLOCATED_SIZE_CHECK' } } + +# - { name: IBF_ISEQ_ENABLE_LOCAL_BUFFER, env: { cppflags: '-DIBF_ISEQ_ENABLE_LOCAL_BUFFER' } } + +# - { name: RGENGC_ESTIMATE_OLDMALLOC, env: { cppflags: '-DRGENGC_ESTIMATE_OLDMALLOC' } } +# - { name: RGENGC_FORCE_MAJOR_GC, env: { cppflags: '-DRGENGC_FORCE_MAJOR_GC' } } +# - { name: RGENGC_OBJ_INFO, env: { cppflags: '-DRGENGC_OBJ_INFO' } } +# - { name: RGENGC_OLD_NEWOBJ_CHECK, env: { cppflags: '-DRGENGC_OLD_NEWOBJ_CHECK' } } +# - { name: RGENGC_PROFILE, env: { cppflags: '-DRGENGC_PROFILE' } } + +# - { name: VM_DEBUG_BP_CHECK, env: { cppflags: '-DVM_DEBUG_BP_CHECK' } } +# - { name: VM_DEBUG_VERIFY_METHOD_CACHE, env: { cppflags: '-DVM_DEBUG_VERIFY_METHOD_CACHE' } } + + - { name: MJIT_FORCE_ENABLE, env: { cppflags: '-DMJIT_FORCE_ENABLE' } } + - { name: YJIT_FORCE_ENABLE, env: { cppflags: '-DYJIT_FORCE_ENABLE' } } name: ${{ matrix.entry.name }} runs-on: ubuntu-latest container: - image: ghcr.io/ruby/ruby-ci-image:${{ matrix.entry.container || 'clang-14' }} + image: ghcr.io/ruby/ruby-ci-image:${{ matrix.entry.container || matrix.entry.env.default_cc || 'clang-15' }} options: --user root - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + env: ${{ matrix.entry.env || matrix.env }} steps: - run: id working-directory: @@ -198,12 +224,11 @@ jobs: working-directory: - name: setenv run: | - echo "${{ matrix.entry.key }}=${{ matrix.entry.value }}" >> $GITHUB_ENV echo "GNUMAKEFLAGS=-sj$((1 + $(nproc --all)))" >> $GITHUB_ENV - - uses: actions/checkout@v2 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@v2 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: src/.downloaded-cache key: downloaded-cache @@ -212,25 +237,32 @@ jobs: - name: Run configure run: > ../src/configure -C ${default_configure} ${append_configure} - ${{ matrix.entry.key == 'crosshost' && '--host="${crosshost}"' || '--with-gcc="${default_cc} ${append_cc}"' }} - ${{ matrix.entry.shared || '--enable-shared' }} + --${{ + matrix.entry.crosshost && 'host' || 'with-gcc' + }}=${{ + matrix.entry.crosshost || '"${default_cc}${append_cc:+ $append_cc}"' + }} + --${{ matrix.entry.shared || 'enable' }}-shared - run: make extract-extlibs - run: make incs + - run: make showflags - run: make - run: make leaked-globals - run: make test - run: make install if: ${{ matrix.entry.check }} - - run: make prepare-gems - if: ${{ matrix.entry.check }} - run: make test-tool if: ${{ matrix.entry.check }} - run: make test-all TESTS='-- ruby -ext-' if: ${{ matrix.entry.check }} - run: make test-spec + env: + CHECK_LEAKS: true if: ${{ matrix.entry.check }} + - run: make test-annocheck + if: ${{ matrix.entry.check && endsWith(matrix.entry.name, 'annocheck') }} - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { @@ -238,7 +270,7 @@ jobs: "env": "${{ github.workflow }} / ${{ matrix.entry.name }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml new file mode 100644 index 0000000000..d8dc58b119 --- /dev/null +++ b/.github/workflows/macos.yml @@ -0,0 +1,113 @@ +name: macOS +on: + push: + paths-ignore: + - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + pull_request: + paths-ignore: + - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + +concurrency: + group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} + cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} + +permissions: + contents: read + +jobs: + make: + strategy: + matrix: + test_task: ["check"] # "test-bundler-parallel", "test-bundled-gems" + os: + - macos-13 + - macos-14 + - macos-15 + fail-fast: false + env: + GITPULLOPTIONS: --no-tags origin ${{github.ref}} + runs-on: ${{ matrix.os }} + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + steps: + - run: mkdir build + working-directory: + - name: git config + run: | + git config --global advice.detachedHead 0 + git config --global init.defaultBranch garbage + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + path: src + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: src/.downloaded-cache + key: downloaded-cache + - name: Install libraries + run: | + brew install gmp libffi openssl@1.1 zlib autoconf automake libtool readline bison + working-directory: src + - name: Set ENV + run: | + echo "MAKEFLAGS=-j$((1 + $(sysctl -n hw.activecpu)))" >> $GITHUB_ENV + echo "PATH="/usr/local/opt/bison/bin:/opt/homebrew/opt/bison/bin:$PATH"" >> $GITHUB_ENV + - run: ./autogen.sh + working-directory: src + - name: Run configure + run: ../src/configure -C --disable-install-doc --with-openssl-dir=$(brew --prefix openssl@1.1) --with-readline-dir=$(brew --prefix readline) + - run: make incs + - run: make prepare-gems + if: ${{ matrix.test_task == 'test-bundled-gems' }} + - run: make + - run: make leaked-globals + if: ${{ matrix.test_task == 'check' }} + - name: make ${{ matrix.test_task }} + run: | + make -s ${{ matrix.test_task }} ${TESTS:+TESTS=`echo "$TESTS" | sed 's| |$$/ -n!/|g;s|^|-n!/|;s|$|$$/|'`} + timeout-minutes: 40 + env: + RUBY_TESTOPTS: "-q --tty=no" + TESTS: ${{ matrix.test_task == 'check' && matrix.skipped_tests || '' }} + TEST_BUNDLED_GEMS_ALLOW_FAILURES: "" + PRECHECK_BUNDLED_GEMS: "no" + - name: make skipped tests + run: | + make -s test-all TESTS=`echo "$TESTS" | sed 's| |$$/ -n/|g;s|^|-n/|;s|$|$$/|'` + env: + GNUMAKEFLAGS: "" + RUBY_TESTOPTS: "-v --tty=no" + TESTS: ${{ matrix.skipped_tests }} + PRECHECK_BUNDLED_GEMS: "no" + if: ${{ matrix.test_task == 'check' && matrix.skipped_tests != '' }} + continue-on-error: ${{ matrix.continue-on-skipped_tests || false }} + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 + with: + payload: | + { + "ci": "GitHub Actions", + "env": "${{ matrix.os }} / ${{ matrix.test_task }}${{ matrix.configure }}", + "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 }} # ruby-lang slack: ruby/simpler-alerts-bot + if: ${{ failure() && github.event_name == 'push' }} + +defaults: + run: + working-directory: build diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 05c813b498..0df917d3d8 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -3,18 +3,32 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' + - '**/.document' pull_request: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} +permissions: + contents: read + # Notes: # Actions console encoding causes issues, see test-all & test-spec steps # @@ -26,23 +40,22 @@ jobs: MSYSTEM: ${{ matrix.msystem }} MSYS2_ARCH: x86_64 CHOST: "x86_64-w64-mingw32" - CFLAGS: "-march=x86-64 -mtune=generic -O3 -pipe -fstack-protector-strong" + CFLAGS: "-march=x86-64 -mtune=generic -O3 -pipe" CXXFLAGS: "-march=x86-64 -mtune=generic -O3 -pipe" CPPFLAGS: "-D_FORTIFY_SOURCE=2 -D__USE_MINGW_ANSI_STDIO=1 -DFD_SETSIZE=2048" - LDFLAGS: "-pipe -fstack-protector-strong" + LDFLAGS: "-pipe" UPDATE_UNICODE: "UNICODE_FILES=. UNICODE_PROPERTY_FILES=. UNICODE_AUXILIARY_FILES=. UNICODE_EMOJI_FILES=." GITPULLOPTIONS: --no-tags origin ${{github.ref}} strategy: matrix: include: - - msystem: "MINGW64" - base_ruby: 2.6 - test_task: "check" # to make job names consistent + # To mitigate flakiness of MinGW CI, we test only one runtime that newer MSYS2 uses. - msystem: "UCRT64" base_ruby: head - test_task: "check" # to make job names consistent + test_task: "check" + test-all-opts: "--name=!/TestObjSpace#test_reachable_objects_during_iteration/" fail-fast: false - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} steps: - run: mkdir build working-directory: @@ -52,25 +65,26 @@ jobs: git config --global core.eol lf git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@v2 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@v2 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: src/.downloaded-cache key: downloaded-cache - name: Set up Ruby & MSYS2 - uses: MSP-Greg/ruby-setup-ruby@win-ucrt-1 + uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 with: ruby-version: ${{ matrix.base_ruby }} - name: set env run: | echo "GNUMAKEFLAGS=-j$((2 * NUMBER_OF_PROCESSORS))" >> $GITHUB_ENV - echo "TEST_JOBS=$((15 * NUMBER_OF_PROCESSORS / 10))" >> $GITHUB_ENV - name: where check run: | # show where + mv /c/Windows/System32/libcrypto-1_1-x64.dll /c/Windows/System32/libcrypto-1_1-x64.dll_ + mv /c/Windows/System32/libssl-1_1-x64.dll /c/Windows/System32/libssl-1_1-x64.dll_ result=true for e in gcc.exe ragel.exe make.exe bison.exe libcrypto-1_1-x64.dll libssl-1_1-x64.dll; do echo '##['group']'$'\033[93m'$e$'\033[m' @@ -79,6 +93,18 @@ jobs: done $result + - name: version check + run: | + # show version + result=true + for e in gcc ragel make bison "openssl version"; do + case "$e" in *" "*) ;; *) e="$e --version";; esac + echo '##['group']'$'\033[93m'$e$'\033[m' + $e || result=false + echo '##['endgroup']' + done + $result + - name: autogen run: | ./autogen.sh @@ -98,7 +124,7 @@ jobs: make update-gems - name: make all - timeout-minutes: 20 + timeout-minutes: 30 run: | make @@ -112,23 +138,28 @@ jobs: timeout-minutes: 5 run: | make test + if: ${{matrix.test_task == 'check' || matrix.test_task == 'test'}} - name: test-all timeout-minutes: 45 run: | # Actions uses UTF8, causes test failures, similar to normal OS setup chcp.com 437 - make test-all + make ${{ StartsWith(matrix.test_task, 'test/') && matrix.test_task || 'test-all' }} env: - RUBY_TESTOPTS: -j${{env.TEST_JOBS}} --retry --job-status=normal --show-skip --timeout-scale=1.5 + RUBY_TESTOPTS: >- + --retry --job-status=normal --show-skip --timeout-scale=1.5 + ${{ matrix.test-all-opts }} BUNDLER_VERSION: + if: ${{matrix.test_task == 'check' || matrix.test_task == 'test-all' || StartsWith(matrix.test_task, 'test/')}} - name: test-spec timeout-minutes: 10 run: | - make test-spec + make ${{ StartsWith(matrix.test_task, 'spec/') && matrix.test_task || 'test-spec' }} + if: ${{matrix.test_task == 'check' || matrix.test_task == 'test-spec' || StartsWith(matrix.test_task, 'spec/')}} - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { @@ -136,7 +167,7 @@ jobs: "env": "${{ github.workflow }} ${{ matrix.msystem }} / ${{ matrix.test_task }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/mjit-bindgen.yml b/.github/workflows/mjit-bindgen.yml new file mode 100644 index 0000000000..26f8a1b2aa --- /dev/null +++ b/.github/workflows/mjit-bindgen.yml @@ -0,0 +1,104 @@ +name: MJIT bindgen +on: + push: + paths-ignore: + - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + pull_request: + paths-ignore: + - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + +concurrency: + group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} + cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} + +permissions: + contents: read + +jobs: + make: + strategy: + matrix: + include: + - task: mjit-bindgen + fail-fast: false + runs-on: ubuntu-22.04 + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + steps: + - run: mkdir build + working-directory: + - name: Set ENV + run: | + echo "GNUMAKEFLAGS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV + - 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 \ + libclang1-14 \ + bison autoconf + sudo apt-get install -q -y pkg-config || : + - name: Set up Ruby + uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 + with: + ruby-version: '3.1' + - name: git config + run: | + git config --global advice.detachedHead 0 + git config --global init.defaultBranch garbage + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + path: src + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: src/.downloaded-cache + key: downloaded-cache + - name: Fixed world writable dirs + run: | + chmod -v go-w $HOME $HOME/.config + sudo chmod -R go-w /usr/share + sudo bash -c 'IFS=:; for d in '"$PATH"'; do chmod -v go-w $d; done' || : + - run: ./autogen.sh + working-directory: src + - name: Run configure + run: ../src/configure -C --disable-install-doc --prefix=$(pwd)/install --enable-yjit=dev_nodebug + - run: make incs + - run: make + - run: make install + - run: make ${{ matrix.task }} + - run: git diff --exit-code + working-directory: src + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 + with: + payload: | + { + "ci": "GitHub Actions", + "env": "${{ matrix.os }} / ${{ matrix.test_task }}${{ matrix.configure }}", + "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 }} # ruby-lang slack: ruby/simpler-alerts-bot + if: ${{ failure() && github.event_name == 'push' }} + +defaults: + run: + working-directory: build diff --git a/.github/workflows/mjit.yml b/.github/workflows/mjit.yml index 75e5b1088c..6f7181489a 100644 --- a/.github/workflows/mjit.yml +++ b/.github/workflows/mjit.yml @@ -3,30 +3,47 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' + - '**/.document' pull_request: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' + - '**/.document' + - '**.[1-8]' + - '**.ronn' + merge_group: + paths-ignore: + - 'doc/**' + - '**.md' + - '**.rdoc' + - '**/.document' + - '**.[1-8]' + - '**.ronn' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} +permissions: + contents: read + jobs: make: strategy: matrix: - test_task: [ "check" ] # to make job names consistent - jit_opts: [ "--mjit", "--mjit-wait" ] + test_task: [check] # to make job names consistent + mjit_opts: [--mjit-wait] fail-fast: false runs-on: ubuntu-latest - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} env: TESTOPTS: '-q --tty=no' - RUN_OPTS: '--disable-gems ${{ matrix.jit_opts }} --mjit-debug=-ggdb3' + RUN_OPTS: '--disable-gems ${{ matrix.mjit_opts }} --mjit-debug=-ggdb3' GITPULLOPTIONS: --no-tags origin ${{github.ref}} steps: - run: mkdir build @@ -40,10 +57,10 @@ jobs: run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@v2 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@v2 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: src/.downloaded-cache key: downloaded-cache @@ -62,31 +79,30 @@ jobs: - run: make incs - run: make - run: sudo make -s install - - run: sudo apt-get install gdb # used by test / test-all failure - name: Run test run: | - ulimit -c unlimited + unset GNUMAKEFLAGS make -s test RUN_OPTS="$RUN_OPTS" timeout-minutes: 60 - - name: Run test-all - run: | - ulimit -c unlimited - make -s test-all RUN_OPTS="$RUN_OPTS" - timeout-minutes: 60 + # - name: Run test-all + # run: | + # ulimit -c unlimited + # make -s test-all RUN_OPTS="$RUN_OPTS" + # timeout-minutes: 60 - name: Run test-spec run: | - ulimit -c unlimited + unset GNUMAKEFLAGS make -s test-spec RUN_OPTS="$RUN_OPTS" timeout-minutes: 60 - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { "ci": "GitHub Actions", - "env": "${{ github.workflow }} / ${{ matrix.test_task }} ${{ matrix.jit_opts }}", + "env": "${{ github.workflow }} / ${{ matrix.test_task }} ${{ matrix.mjit_opts }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000000..5d4474d978 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,18 @@ +name: Start release workflow +on: + push: + tags: + - '*' + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Build release package + run: | + curl -L -X POST \ + -H "Authorization: Bearer ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }}" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/ruby/actions/dispatches \ + -d '{"event_type": "${{ github.ref }}"}' diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml new file mode 100644 index 0000000000..c12a95362d --- /dev/null +++ b/.github/workflows/scorecards.yml @@ -0,0 +1,72 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: Scorecards 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 + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '22 4 * * 2' + push: + branches: [ "master" ] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecards analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + # Uncomment the permissions below if installing in a private repository. + # contents: read + # actions: read + + steps: + - name: "Checkout code" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@ea651e62978af7915d09fe2e282747c798bf2dab # v2.4.1 + with: + results_file: results.sarif + results_format: sarif + # (Optional) Read-only 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 }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories: + # - `publish_results` will always be set to `false`, regardless + # of the value entered here. + publish_results: true + + # 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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + 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@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2.1.27 + with: + sarif_file: results.sarif diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index 3f829650d5..4521195a2b 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -2,43 +2,61 @@ name: Rubyspec Version Guards Check on: push: - paths-ignore: - - 'doc/**' - - '**.md' - - '**.rdoc' + paths: + - 'spec/**' + - '!spec/*.md' pull_request: - paths-ignore: - - 'doc/**' - - '**.md' - - '**.rdoc' + paths: + - 'spec/**' + - '!spec/*.md' + merge_group: concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} +permissions: + contents: read + jobs: rubyspec: name: Rubyspec - runs-on: ubuntu-20.04 - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + + runs-on: ubuntu-22.04 + + if: >- + ${{!(false + || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.head_commit.message, 'Document') + || contains(github.event.pull_request.title, '[DOC]') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') + || (github.event_name == 'push' && github.actor == 'dependabot[bot]') + )}} + strategy: matrix: # Specs from ruby/spec should still run on all supported Ruby versions. # This also ensures the needed ruby_version_is guards are there, see spec/README.md. ruby: - - ruby-2.7 - - ruby-3.0 + - ruby-3.1 + - ruby-3.2 steps: - - uses: actions/checkout@v2 - - uses: ruby/setup-ruby@v1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 with: ruby-version: ${{ matrix.ruby }} bundler: none + - run: gem install webrick + - run: ruby ../mspec/bin/mspec working-directory: spec/ruby - - uses: k0kubun/action-slack@v2.0.0 + env: + CHECK_LEAKS: true + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { @@ -46,8 +64,8 @@ jobs: "env": "${{ github.workflow }} / rubyspec @ ${{ matrix.ruby }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: ${{ failure() && github.event_name == 'push' }} + if: ${{ failure() }} diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index ff79dad880..4fbca1170e 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -3,54 +3,69 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' + - '**/.document' pull_request: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} +permissions: + contents: read + jobs: make: strategy: matrix: - test_task: ["check", "test-bundler-parallel", "test-bundled-gems"] - os: - - ubuntu-20.04 -# - ubuntu-18.04 - configure: ["", "cppflags=-DRUBY_DEBUG"] + # main variables included in the job name + test_task: [check] + configure: [cppflags=-DRUBY_DEBUG] # default to use more assertions + arch: [''] + # specify all jobs with `include` to avoid testing duplicated things include: - - test_task: "check" - os: ubuntu-20.04 - configure: "--host=i686-$OSTYPE" - - test_task: "test-all TESTS=--repeat-count=2" - os: ubuntu-20.04 - configure: "" + - test_task: check + - test_task: check + arch: i686 + configure: '' # test without -DRUBY_DEBUG as well + - test_task: check + configure: "--enable-shared --enable-load-relative" + - test_task: test-all TESTS=--repeat-count=2 + - test_task: test-bundler-parallel + - test_task: test-bundled-gems fail-fast: false env: GITPULLOPTIONS: --no-tags origin ${{github.ref}} RUBY_DEBUG: ci - runs-on: ${{ matrix.os }} - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + SETARCH: ${{ matrix.arch && format('setarch {0}', matrix.arch) }} + runs-on: ubuntu-22.04 + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} steps: - run: mkdir build working-directory: - name: Set ENV - env: - configure: ${{matrix.configure}} run: | echo "GNUMAKEFLAGS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV - arch=`echo " $configure" | sed '/.* --host=/!d;s///;s/[- ].*//'` - echo "SETARCH=${arch:+setarch $arch}" >> $GITHUB_ENV - name: Install libraries + env: + arch: ${{matrix.arch}} run: | set -x - arch="${SETARCH##* }" arch=${arch:+:${arch/i[3-6]86/i386}} ${arch:+sudo dpkg --add-architecture ${arch#:}} sudo apt-get update -q || : @@ -64,10 +79,10 @@ jobs: run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@v2 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@v2 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: src/.downloaded-cache key: downloaded-cache @@ -83,42 +98,44 @@ jobs: arch: ${{matrix.arch}} run: >- $SETARCH ../src/configure -C --disable-install-doc ${{ matrix.configure }} - ${arch:+--target=$arch-$OSTYPE} + ${arch:+--target=$arch-$OSTYPE --host=$arch-$OSTYPE} - run: $SETARCH make incs + - run: $SETARCH make prepare-gems + if: ${{ matrix.test_task == 'test-bundled-gems' }} - run: $SETARCH make - run: $SETARCH make leaked-globals if: ${{ matrix.test_task == 'check' }} - - run: $SETARCH make prepare-gems - if: ${{ matrix.test_task == 'check' }} - name: Create dummy files in build dir run: | $SETARCH ./miniruby -e '(("a".."z").to_a+("A".."Z").to_a+("0".."9").to_a+%w[foo bar test zzz]).each{|basename|File.write("#{basename}.rb", "raise %(do not load #{basename}.rb)")}' if: ${{ matrix.test_task == 'check' }} - name: make ${{ matrix.test_task }} run: | - $SETARCH make -s ${{ matrix.test_task }} ${TESTS:+TESTS=`echo "$TESTS" | sed 's| |$/ -n!/|g;s|^|-n!/|;s|$|$$/|'`} + $SETARCH make -s ${{ matrix.test_task }} ${TESTS:+TESTS=`echo "$TESTS" | sed 's| |$$/ -n!/|g;s|^|-n!/|;s|$|$$/|'`} timeout-minutes: 40 env: RUBY_TESTOPTS: "-q --tty=no" TESTS: ${{ matrix.test_task == 'check' && matrix.skipped_tests || '' }} TEST_BUNDLED_GEMS_ALLOW_FAILURES: "" + PRECHECK_BUNDLED_GEMS: "no" - name: make skipped tests run: | - $SETARCH make -s test-all TESTS=`echo "$TESTS" | sed 's| |$/ -n/|g;s|^|-n/|;s|$|$$/|'` + $SETARCH make -s test-all TESTS=`echo "$TESTS" | sed 's| |$$/ -n/|g;s|^|-n/|;s|$|$$/|'` env: GNUMAKEFLAGS: "" RUBY_TESTOPTS: "-v --tty=no" TESTS: ${{ matrix.skipped_tests }} if: ${{ matrix.test_task == 'check' && matrix.skipped_tests != '' }} - - uses: k0kubun/action-slack@v2.0.0 + continue-on-error: ${{ matrix.continue-on-skipped_tests || false }} + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { "ci": "GitHub Actions", - "env": "${{ matrix.os }} / ${{ matrix.test_task }}${{ matrix.configure }}", + "env": "${{ github.workflow }} / ${{ matrix.test_task }} ${{ matrix.configure }}${{ matrix.arch }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml new file mode 100644 index 0000000000..27920b5821 --- /dev/null +++ b/.github/workflows/wasm.yml @@ -0,0 +1,146 @@ +name: WebAssembly +on: + push: + paths-ignore: + - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + pull_request: + paths-ignore: + - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + +concurrency: + group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} + cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} + +permissions: # added using https://github.com/step-security/secure-workflows + contents: read + +jobs: + make: + strategy: + matrix: + entry: +# # wasmtime can't compile non-optimized Asyncified binary due to locals explosion +# - { name: O0-debuginfo, optflags: "-O0", debugflags: "-g", wasmoptflags: "-O1" } +# - { name: O1, optflags: "-O1", debugflags: "" , wasmoptflags: "-O1" } + - { name: O2, optflags: "-O2", debugflags: "" , wasmoptflags: "-O2" } +# - { name: O3, optflags: "-O3", debugflags: "" , wasmoptflags: "-O3" } +# # -O4 is equivalent to -O3 in clang, but it's different in wasm-opt +# - { name: O4, optflags: "-O3", debugflags: "" , wasmoptflags: "-O4" } +# - { name: Oz, optflags: "-Oz", debugflags: "" , wasmoptflags: "-Oz" } + fail-fast: false + env: + RUBY_TESTOPTS: '-q --tty=no' + GITPULLOPTIONS: --no-tags origin ${{github.ref}} + WASI_SDK_VERSION_MAJOR: 14 + WASI_SDK_VERSION_MINOR: 0 + BINARYEN_VERSION: 109 + WASMTIME_VERSION: v0.33.0 + runs-on: ubuntu-22.04 + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + steps: + - run: mkdir build + working-directory: + - name: git config + run: | + git config --global advice.detachedHead 0 + git config --global init.defaultBranch garbage + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + path: src + - name: Install libraries + run: | + set -ex + sudo apt-get update -q || : + sudo apt-get install --no-install-recommends -q -y ruby bison make autoconf git wget + + wasi_sdk_deb="wasi-sdk_${WASI_SDK_VERSION_MAJOR}.${WASI_SDK_VERSION_MINOR}_amd64.deb" + wget "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION_MAJOR}/${wasi_sdk_deb}" + sudo dpkg -i "$wasi_sdk_deb" + rm -f "$wasi_sdk_deb" + + mkdir build-sdk + pushd build-sdk + + wasmtime_url="https://github.com/bytecodealliance/wasmtime/releases/download/${WASMTIME_VERSION}/wasmtime-${WASMTIME_VERSION}-x86_64-linux.tar.xz" + wget -O - "$wasmtime_url" | tar xJf - + sudo ln -fs "$PWD/wasmtime-${WASMTIME_VERSION}-x86_64-linux/wasmtime" /usr/local/bin/wasmtime + + binaryen_tarball="binaryen-version_${BINARYEN_VERSION}-x86_64-linux.tar.gz" + binaryen_url="https://github.com/WebAssembly/binaryen/releases/download/version_${BINARYEN_VERSION}/${binaryen_tarball}" + wget -O - "$binaryen_url" | tar xfz - + sudo ln -fs "$PWD/binaryen-version_${BINARYEN_VERSION}/bin/wasm-opt" /usr/local/bin/wasm-opt + working-directory: src + - name: Set ENV + run: | + echo "MAKEFLAGS=-j$((1 + $(sysctl -n hw.activecpu)))" >> $GITHUB_ENV + echo "WASI_SDK_PATH=/opt/wasi-sdk" >> $GITHUB_ENV + - run: ./autogen.sh + working-directory: src + + - uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 + with: + ruby-version: '3.0' + bundler: none + + - name: Download config.guess with wasi version + run: | + rm tool/config.guess tool/config.sub + ruby tool/downloader.rb -d tool -e gnu config.guess config.sub + working-directory: src + + - name: Run configure + run: | + ../src/configure \ + --host wasm32-unknown-wasi \ + --with-static-linked-ext \ + LDFLAGS=" \ + -Xlinker --stack-first \ + -Xlinker -z -Xlinker stack-size=16777216 \ + " \ + optflags="${{ matrix.entry.optflags }}" \ + debugflags="${{ matrix.entry.debugflags }}" \ + wasmoptflags="${{ matrix.entry.wasmoptflags }} ${{ matrix.entry.debugflags }}" + + # miniruby may not be built when cross-compling + - run: make mini ruby + - name: Run basictest + run: wasmtime run ./../build/miniruby --mapdir /::./ -- basictest/test.rb + working-directory: src + - name: Run bootstraptest (no thread) + run: | + NO_THREAD_TESTS="$(grep -L Thread -R ./bootstraptest | awk -F/ '{ print $NF }' | uniq | sed -n 's/test_\(.*\).rb/\1/p' | paste -s -d, -)" + ruby ./bootstraptest/runner.rb --ruby="$(which wasmtime) run $PWD/../build/ruby --mapdir /::./ -- " --verbose "--sets=$NO_THREAD_TESTS" + working-directory: src + + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 + with: + payload: | + { + "ci": "GitHub Actions", + "env": "${{ github.workflow }} / ${{ matrix.name }}", + "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 }} # ruby-lang slack: ruby/simpler-alerts-bot + if: ${{ failure() && github.event_name == 'push' }} + +defaults: + run: + working-directory: build diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 574bfbf474..c2bd4881c2 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -3,119 +3,133 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' + - '**/.document' pull_request: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} +permissions: + contents: read + jobs: make: strategy: matrix: include: - - vs: 2019 - os: windows-2019 - vcvars: '"C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat"' - # - vs: 2022 - # os: windows-2022 - # vcvars: '"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"' + - vs: 2022 + vcvers: -vcvars_ver=14.2 fail-fast: false - runs-on: ${{ matrix.os }} - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + runs-on: windows-${{ matrix.vs }} + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} name: VisualStudio ${{ matrix.vs }} env: GITPULLOPTIONS: --no-tags origin ${{github.ref}} - VCVARS: ${{ matrix.vcvars }} PATCH: C:\msys64\usr\bin\patch.exe + OS_VER: windows-${{ matrix.vs }} steps: - run: md build working-directory: - - uses: msys2/setup-msys2@v2 + - uses: msys2/setup-msys2@61f9e5e925871ba6c9e3e8da24ede83ea27fa91f # v2.27.0 id: setup-msys2 with: update: true - install: >- - patch - if: ${{ matrix.os != 'windows-2019' }} + install: bison patch - name: patch path shell: msys2 {0} run: echo PATCH=$(cygpath -wa $(command -v patch)) >> $GITHUB_ENV if: ${{ steps.setup-msys2.outcome == 'success' }} - - uses: actions/cache@v2 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: - path: C:\vcpkg\downloads - key: ${{ runner.os }}-vcpkg-download-${{ matrix.os }}-${{ github.sha }} + path: C:\vcpkg\installed + key: ${{ runner.os }}-vcpkg-installed-windows-${{ matrix.vs }}-${{ github.sha }} restore-keys: | - ${{ runner.os }}-vcpkg-download-${{ matrix.os }}- - ${{ runner.os }}-vcpkg-download- + ${{ runner.os }}-vcpkg-installed-windows-${{ matrix.vs }}- + ${{ runner.os }}-vcpkg-installed-windows- - name: Install libraries with vcpkg run: | - vcpkg --triplet x64-windows install readline zlib - - uses: actions/cache@v2 - with: - path: C:\Users\runneradmin\AppData\Local\Temp\chocolatey - key: ${{ runner.os }}-chocolatey-${{ matrix.os }}-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-chocolatey-${{ matrix.os }}- - ${{ runner.os }}-chocolatey- - - name: Install libraries with chocolatey - run: | - # Using Choco-Install for retries, but it doesn't detect failures properly - # if you pass multiple package names in a single command. - Choco-Install -PackageName openssl - Choco-Install -PackageName winflexbison3 - shell: pwsh + iex "& {$(irm get.scoop.sh)} -RunAsAdmin" + Join-Path (Resolve-Path ~).Path "scoop\shims" >> $Env:GITHUB_PATH + scoop install cmake@3.31.6 + vcpkg --triplet x64-windows install libffi libyaml openssl readline zlib + shell: + pwsh - name: git config run: | git config --global core.autocrlf false git config --global core.eol lf git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@v2 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@v2 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: src/.downloaded-cache key: downloaded-cache - name: setup env # %TEMP% is inconsistent with %TMP% and test-all expects they are consistent. # https://github.com/actions/virtual-environments/issues/712#issuecomment-613004302 + # msys2/setup-msys2 installs MSYS2 to D:/a/_temp/msys64/usr/bin run: | + set Path=D:/a/_temp/msys64/usr/bin;%Path% + if not "%VCVARS%" == "" goto :vcset + set VCVARS="C:\Program Files (x86)\Microsoft Visual Studio\${{ matrix.vs }}\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + if not exist %VCVARS% set VCVARS="C:\Program Files\Microsoft Visual Studio\${{ matrix.vs }}\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + :vcset set | C:\msys64\usr\bin\sort > old.env - call %VCVARS% + call %VCVARS% ${{ matrix.vcvers || ''}} set TMP=%USERPROFILE%\AppData\Local\Temp set TEMP=%USERPROFILE%\AppData\Local\Temp set /a TEST_JOBS=(15 * %NUMBER_OF_PROCESSORS% / 10) > nul set | C:\msys64\usr\bin\sort > new.env C:\msys64\usr\bin\comm -13 old.env new.env >> %GITHUB_ENV% del *.env - - name: Configure + - name: compiler version + run: cl + - name: link libraries run: | - ../src/win32/configure.bat --disable-install-doc --enable-bundled-libffi --with-opt-dir=C:/vcpkg/installed/x64-windows --with-openssl-dir="C:/Program Files/OpenSSL-Win64" + for %%I in (C:\vcpkg\installed\x64-windows\bin\*.dll) do ( + if not %%~nI == readline mklink %%~nxI %%I + ) + for %%I in (libcrypto-1_1-x64 libssl-1_1-x64) do ( + ren c:\Windows\System32\%%I.dll %%I.dll_ + ) + - name: Configure + run: >- + ../src/win32/configure.bat --disable-install-doc + --with-opt-dir=C:/vcpkg/installed/x64-windows - run: nmake incs - run: nmake extract-extlibs - run: nmake env: - YACC: win_bison + YACC: bison.exe - run: nmake test timeout-minutes: 5 + - run: nmake test-spec + timeout-minutes: 10 - run: nmake test-all env: RUBY_TESTOPTS: -j${{env.TEST_JOBS}} --job-status=normal timeout-minutes: 60 - continue-on-error: ${{ matrix.continue-on-error || false }} - - run: nmake test-spec - timeout-minutes: 10 - continue-on-error: ${{ matrix.continue-on-error || false }} - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { @@ -123,7 +137,7 @@ jobs: "env": "VS${{ matrix.vs }} / ${{ matrix.test_task || 'check' }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index c479b8a624..0b7b9046e9 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -3,47 +3,90 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' + - '**/.document' pull_request: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} +permissions: + contents: read + jobs: + cargo: + name: Rust cargo test + # GitHub Action's image seems to already contain a Rust 1.58.0. + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + # For now we can't run cargo test --offline because it complains about the + # capstone dependency, even though the dependency is optional + #- run: cargo test --offline + - run: RUST_BACKTRACE=1 cargo test + working-directory: yjit + # Also compile and test with all features enabled + - run: RUST_BACKTRACE=1 cargo test --all-features + working-directory: yjit + # Check that we can build in release mode too + - run: cargo build --release + working-directory: yjit make: strategy: + fail-fast: false matrix: - test_task: ["check"] # "test-bundler-parallel", - os: - - ubuntu-20.04 -# - ubuntu-18.04 - yjit_opts: [ - "--yjit", - "--yjit --yjit-call-threshold=1", - ] - configure: ["", "cppflags=-DRUBY_DEBUG"] include: + - test_task: 'yjit-bindgen' + hint: 'To fix: use patch in logs' + configure: '--with-gcc=clang-14 --enable-yjit=dev' + libclang_path: '/usr/lib/llvm-14/lib/libclang.so.1' + + - test_task: "check" + # YJIT should be automatically built in release mode on x86-64 Linux with rustc present + #configure: "--enable-yjit RUSTC='rustc +1.58.0'" + configure: "RUSTC='rustc +1.58.0'" + rust_version: "1.58.0" + + - test_task: "check" + configure: "--enable-yjit=dev" + + - test_task: "check" + configure: "--enable-yjit=dev" + yjit_opts: "--yjit-call-threshold=1 --yjit-verify-ctx" + - test_task: "test-all TESTS=--repeat-count=2" - os: ubuntu-20.04 - configure: "" - yjit_enable_env: RUBY_YJIT_ENABLE + configure: "--enable-yjit=dev" + - test_task: "test-bundled-gems" - os: ubuntu-20.04 - configure: "cppflags=-DRUBY_DEBUG" - yjit_enable_env: RUBY_YJIT_ENABLE - fail-fast: false + configure: "--enable-yjit=dev" + + - test_task: "yjit-bench" + configure: "--enable-yjit=dev" + yjit_bench_opts: "--yjit-stats" env: GITPULLOPTIONS: --no-tags origin ${{github.ref}} RUN_OPTS: ${{ matrix.yjit_opts }} + YJIT_BENCH_OPTS: ${{ matrix.yjit_bench_opts }} RUBY_DEBUG: ci - runs-on: ${{ matrix.os }} - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + BUNDLE_JOBS: 8 # for yjit-bench + runs-on: ubuntu-22.04 + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} steps: - run: mkdir build working-directory: @@ -52,14 +95,17 @@ jobs: 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 ruby + - name: Install Rust + if: ${{ matrix.rust_version }} + run: rustup install ${{ matrix.rust_version }} --profile minimal - name: git config run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@v2 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@v2 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: src/.downloaded-cache key: downloaded-cache @@ -74,12 +120,12 @@ jobs: - run: ./autogen.sh working-directory: src - name: Run configure - run: ../src/configure -C --disable-install-doc ${{ matrix.configure }} + run: ../src/configure -C --disable-install-doc --prefix=$(pwd)/install ${{ matrix.configure }} - run: make incs - - run: make - - run: make leaked-globals - if: ${{ matrix.test_task == 'check' }} - run: make prepare-gems + if: ${{ matrix.test_task == 'test-bundled-gems' }} + - run: make -j + - run: make leaked-globals if: ${{ matrix.test_task == 'check' }} - name: Create dummy files in build dir run: | @@ -87,21 +133,33 @@ jobs: if: ${{ matrix.test_task == 'check' }} - name: Enable YJIT through ENV run: echo "RUBY_YJIT_ENABLE=1" >> $GITHUB_ENV - if: ${{ matrix.yjit_enable_env }} - - run: make -s ${{ matrix.test_task }} RUN_OPTS="$RUN_OPTS" + # Check that the binary was built with YJIT + - name: Check YJIT enabled + run: ./miniruby --yjit -v | grep "+YJIT" + - name: make ${{ matrix.test_task }} + run: make -s -j ${{ matrix.test_task }} RUN_OPTS="$RUN_OPTS" YJIT_BENCH_OPTS="$YJIT_BENCH_OPTS" timeout-minutes: 60 env: RUBY_TESTOPTS: "-q --tty=no" TEST_BUNDLED_GEMS_ALLOW_FAILURES: "" - - uses: k0kubun/action-slack@v2.0.0 + PRECHECK_BUNDLED_GEMS: "no" + LIBCLANG_PATH: ${{ matrix.libclang_path }} + continue-on-error: ${{ matrix.test_task == 'yjit-bench' }} + - name: Show ${{ github.event.pull_request.base.ref }} GitHub URL for yjit-bench comparison + run: echo "https://github.com/${BASE_REPO}/commit/${BASE_SHA}" + env: + BASE_REPO: ${{ github.event.pull_request.base.repo.full_name }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + if: ${{ matrix.test_task == 'yjit-bench' && startsWith(github.event_name, 'pull') }} + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { "ci": "GitHub Actions", - "env": "${{ matrix.os }} / ${{ matrix.test_task }}${{ matrix.configure }}", + "env": "${{ github.workflow }} / ${{ matrix.test_task }} ${{ matrix.configure }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/yjit_asm_tests.yml b/.github/workflows/yjit_asm_tests.yml deleted file mode 100644 index 8a9052dd41..0000000000 --- a/.github/workflows/yjit_asm_tests.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: YJIT x86 assembler tests - -on: - push: - paths-ignore: - - 'doc/**' - - '**.md' - - '**.rdoc' - pull_request: - paths-ignore: - - 'doc/**' - - '**.md' - - '**.rdoc' - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -jobs: - test: - runs-on: ubuntu-latest - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} - steps: - - name: Install dependencies - run: | - set -x - sudo apt-get update -q || : - sudo apt-get install --no-install-recommends -q -y build-essential - - name: git config - run: | - git config --global advice.detachedHead 0 - git config --global init.defaultBranch garbage - - uses: actions/checkout@v2 - with: - path: src - - name: Run ASM tests - run: ./misc/test_yjit_asm.sh - working-directory: src diff --git a/.gitignore b/.gitignore index 31bfd787a7..99d32a1825 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ *.inc *.log *.o +*.o.tmp *.obj *.old *.orig @@ -25,6 +26,7 @@ *.sav *.sl *.so +*.so.* *.swp *.yarb *~ @@ -127,6 +129,7 @@ lcov*.info /ruby-runner /ruby-runner.h /ruby-man.rd.gz +/rubyspec_temp /run.gdb /sizes.c /static-ruby @@ -144,6 +147,8 @@ lcov*.info /bin/*.exe /bin/*.dll +/bin/goruby +/bin/ruby # /benchmark/ /benchmark/bm_require.data @@ -218,6 +223,9 @@ lcov*.info /lib/ruby/[1-9]*.* /lib/ruby/vendor_ruby +# /misc/ +/misc/**/__pycache__ + # /spec/bundler /.rspec_status @@ -229,6 +237,14 @@ lcov*.info /win32/*.ico # MJIT -/rb_mjit_header.h -/mjit_config.h /include/ruby-*/*/rb_mjit_min_header-*.h +/lib/ruby_vm/mjit/instruction.rb +/mjit_config.h +/rb_mjit_header.h + +# YJIT +/yjit-bench +/yjit_exit_locations.dump + +# /wasm/ +/wasm/tests/*.wasm diff --git a/.indent.pro b/.indent.pro new file mode 100644 index 0000000000..1d61cbcad1 --- /dev/null +++ b/.indent.pro @@ -0,0 +1,32 @@ +-bap +-nbbb +-nbc +-br +-brs +-nbs +-ncdb +-nce +-cdw +-cli2 +-cbi2 +-ndj +-ncs +-nfc1 +-i4 +-l120 +-lp +-npcs +-psl +-sc +-sob +-sbi4 +-nut +-par + +-TID +-TVALUE +-Tst_data_t +-Tst_index_t +-Tst_table +-Trb_data_type_t +-TFILE diff --git a/.rdoc_options b/.rdoc_options new file mode 100644 index 0000000000..760507c7a2 --- /dev/null +++ b/.rdoc_options @@ -0,0 +1,4 @@ +--- +page_dir: doc +main_page: README.md +title: Documentation for Ruby development version diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f8c66f715e..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,236 +0,0 @@ -# -*- YAML -*- -# Copyright (C) 2011 Urabe, Shyouhei. All rights reserved. -# -# This file is a part of the programming language Ruby. Permission is hereby -# granted, to either redistribute or modify this file, provided that the -# conditions mentioned in the file COPYING are met. Consult the file for -# details. - -# We only manage non-amd64 free pipelines. -# https://docs.travis-ci.com/user/billing-overview/ - -language: c - -os: linux - -if: commit_message !~ /^\[DOC\]/ - -dist: focal - -git: - quiet: true - -cache: - ccache: true - directories: - - $HOME/config_2nd - - $HOME/.downloaded-cache - -env: - global: - # The tests skipped in `make test-all`. - - TEST_ALL_SKIPPED_TESTS= - # The tests executed separately by `make test-all`. - - TEST_ALL_SEPARATED_TESTS= - # Reset timestamps early - - _=$(touch NEWS && find . -type f -exec touch -r NEWS {} +) - - CONFIGURE_TTY=no - - CCACHE_COMPILERCHECK=none - - CCACHE_NOCOMPRESS=1 - - CCACHE_MAXSIZE=512Mi - - NPROC="`nproc`" - # JOBS and SETARCH are overridden when necessary; see below. - - JOBS=-j$((1+${NPROC})) - - SETARCH= - - RUBY_PREFIX=/tmp/ruby-prefix - - GEMS_FOR_TEST='timezone tzinfo' - # https://github.com/travis-ci/travis-build/blob/e411371dda21430a60f61b8f3f57943d2fe4d344/lib/travis/build/bash/travis_apt_get_options.bash#L7 - - travis_apt_get_options='--allow-downgrades --allow-remove-essential --allow-change-held-packages' - - travis_apt_get_options="-yq --no-install-suggests --no-install-recommends $travis_apt_get_options" - # -O1 is faster than -O3 in our tests. - - optflags=-O1 - # -g0 disables backtraces when SEGV. Do not set that. - - debugflags=-ggdb3 - -.org.ruby-lang.ci.matrix-definitions: - - - &gcc-10 - compiler: gcc-10 - before_install: - - tool/travis_retry.sh sudo bash -c "rm -rf '${TRAVIS_ROOT}/var/lib/apt/lists/'* && exec apt-get update -yq" - - >- - tool/travis_retry.sh sudo -E apt-get $travis_apt_get_options install - ccache - gcc-10 - g++-10 - libffi-dev - libgdbm-dev - libncurses-dev - libncursesw5-dev - libreadline-dev - libssl-dev - libyaml-dev - openssl - zlib1g-dev - - # -------- - - - &arm64-linux - name: arm64-linux - arch: arm64 - <<: *gcc-10 - - - &ppc64le-linux - name: ppc64le-linux - arch: ppc64le - <<: *gcc-10 - - - &s390x-linux - name: s390x-linux - arch: s390x - <<: *gcc-10 - - - &arm32-linux - name: arm32-linux - arch: arm64 - # https://packages.ubuntu.com/focal/crossbuild-essential-armhf - compiler: arm-linux-gnueabihf-gcc - env: - - SETARCH='setarch linux32 --verbose --32bit' - # The "TestReadline#test_interrupt_in_other_thread" started failing on arm32 - # from https://www.travis-ci.com/github/ruby/ruby/jobs/529005145 - - TEST_ALL_SKIPPED_TESTS=test_interrupt_in_other_thread - before_install: - - sudo dpkg --add-architecture armhf - - tool/travis_retry.sh sudo bash -c "rm -rf '${TRAVIS_ROOT}/var/lib/apt/lists/'* && exec apt-get update -yq" - - >- - tool/travis_retry.sh sudo -E apt-get $travis_apt_get_options install - ccache - crossbuild-essential-armhf - libc6:armhf - libstdc++-10-dev:armhf - libffi-dev:armhf - libgdbm-dev:armhf - libncurses-dev:armhf - libncursesw5-dev:armhf - libreadline-dev:armhf - libssl-dev:armhf - linux-libc-dev:armhf - zlib1g-dev:armhf - -matrix: - include: - # Build every commit (Allowed Failures): - - <<: *arm32-linux - # Comment out as the 2nd arm64 pipeline is unstable. - # - <<: *arm64-linux - - <<: *ppc64le-linux - - <<: *s390x-linux - allow_failures: - # We see multiple errors indicating errors on the Travis environment itself in a short while: - # https://app.travis-ci.com/github/ruby/ruby/jobs/544382885 - # https://app.travis-ci.com/github/ruby/ruby/jobs/544361370 - # It's not a fault of Ruby's arm32 support but just Travis arm32 seems unsable. - - name: arm32-linux - # - name: arm64-linux - # We see "Some worker was crashed." in about 40% of recent ppc64le-linux jobs - # e.g. https://app.travis-ci.com/github/ruby/ruby/jobs/530959548 - - name: ppc64le-linux - # Tentatively disable, because often hungs up **after** all tests - # have finished successfully and saving caches. - - name: s390x-linux - fast_finish: true - -before_script: - - . tool/ci_functions.sh - - |- - if [ -n "${TEST_ALL_SKIPPED_TESTS}" ]; then - TEST_ALL_OPTS="${TEST_ALL_OPTS} $(ci_to_excluded_test_opts "${TEST_ALL_SKIPPED_TESTS}")" - if [ -z "${TEST_ALL_SEPARATED_TESTS}" ]; then - TEST_ALL_SEPARATED_TESTS="${TEST_ALL_SKIPPED_TESTS}" - fi - fi - - |- - if [ -n "${TEST_ALL_SEPARATED_TESTS}" ]; then - TEST_ALL_OPTS_SEPARATED="$(ci_to_included_test_opts "${TEST_ALL_SEPARATED_TESTS}")" - fi - - echo TEST_ALL_OPTS="${TEST_ALL_OPTS}" TEST_ALL_OPTS_SEPARATED="${TEST_ALL_OPTS_SEPARATED}" - - rm -fr .ext autom4te.cache - - |- - [ -d ~/.downloaded-cache ] || - mkdir ~/.downloaded-cache - - ln -s ~/.downloaded-cache - - "> config.status" - - "> .rbconfig.time" - - sed -f tool/prereq.status template/Makefile.in common.mk > Makefile - - make -s $JOBS up - - make -s $JOBS srcs - - rm -f config.status Makefile rbconfig.rb .rbconfig.time - - |- - if [ -d ~/config_2nd ]; then - cp -pr ~/config_2nd build - else - mkdir build - fi - - mkdir config_1st config_2nd - - chmod -R a-w . - - chmod -R u+w build config_1st config_2nd - - cd build - - |- - case "$CC" in - gcc*) CC="ccache $CC${GCC_FLAGS:+ }$GCC_FLAGS -fno-diagnostics-color";; - clang*) CC="ccache $CC${GCC_FLAGS:+ }$GCC_FLAGS -fno-color-diagnostics";; - esac - - |- - [ ! -f config.cache ] || - [ "$CC" = "`sed -n s/^ac_cv_prog_CC=//p config.cache`" ] || - (set -x; exec rm config.cache) - - $SETARCH ../configure -C --disable-install-doc --prefix=$RUBY_PREFIX $CONFIG_FLAG - - cp -pr config.cache config.status .ext/include ../config_1st - - $SETARCH make reconfig - - cp -pr config.cache config.status .ext/include ../config_2nd - - (cd .. && exec diff -ru config_1st config_2nd) - - chmod u+w .. - - rm -rf ~/config_2nd - - mv ../config_2nd ~ - - chmod u-w .. - - $SETARCH make -s $JOBS - - make -s install - - |- - [ -z "${GEMS_FOR_TEST}" ] || - $RUBY_PREFIX/bin/gem install --no-document $GEMS_FOR_TEST - - echo "raise 'do not load ~/.irbrc in test'" > ~/.irbrc - -script: - - $SETARCH make -s test -o showflags TESTOPTS="${TESTOPTS=$JOBS -q --tty=no}" - - ../tool/travis_wait.sh $SETARCH make -s test-all -o exts TESTOPTS="$JOBS -q --tty=no ${TEST_ALL_OPTS}" RUBYOPT="-w" - # Run the failing tests separately returning ok status to check if it works, - # visualize them. - - | - if [ -n "${TEST_ALL_OPTS_SEPARATED}" ]; then - $SETARCH make -s test-all -o exts TESTOPTS="$JOBS -v --tty=no ${TEST_ALL_OPTS_SEPARATED}" RUBYOPT="-w" || : - fi - - $SETARCH make -s test-spec MSPECOPT=-ff # not using `-j` because sometimes `mspec -j` silently dies - - $SETARCH make -s -o showflags leaked-globals - -# We enable Travis on the specific branches or forked repositories here. -if: (repo = ruby/ruby AND (branch = master OR branch =~ /^ruby_\d_\d$/)) OR repo != ruby/ruby - -# We want to be notified when something happens. -notifications: - irc: - channels: - - "chat.freenode.net#ruby-core" - on_success: change # [always|never|change] # default: always - on_failure: always # [always|never|change] # default: always - template: - - "%{message} by @%{author}: See %{build_url}" - - webhooks: - urls: - - secure: mRsoS/UbqDkKkW5p3AEqM27d4SZnV6Gsylo3bm8T/deltQzTsGzZwrm7OIBXZv0UFZdE68XmPlyHfZFLSP2V9QZ7apXMf9/vw0GtcSe1gchtnjpAPF6lYBn7nMCbVPPx9cS0dwL927fjdRM1vj7IKZ2bk4F0lAJ25R25S6teqdk= # ruby-lang slack: ruby/simpler-alerts-bot (travis) - on_success: never - on_failure: always - - email: - - jaruga@ruby-lang.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7363c106a2..13df6087ca 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1 @@ -Please see the [official issue tracker], [doc/contributing.rdoc] and wiki [HowToContribute]. - -[official issue tracker]: https://bugs.ruby-lang.org -[doc/contributing.rdoc]: contributing.rdoc -[HowToContribute]: https://bugs.ruby-lang.org/projects/ruby/wiki/HowToContribute +See ["Contributing to Ruby"](https://docs.ruby-lang.org/en/master/contributing_md.html), which includes setup and build instructions. @@ -979,7 +979,6 @@ mentioned below. {MIT License}[rdoc-label:label-MIT+License] [lib/rubygems/resolver/molinillo] -[lib/bundler/vendor/molinillo] molinillo is under the following license. @@ -988,6 +987,15 @@ mentioned below. {MIT License}[rdoc-label:label-MIT+License] +[lib/bundler/vendor/pub_grub] + + pub_grub is under the following license. + + >>> + Copyright (c) 2018 John Hawthorn + + {MIT License}[rdoc-label:label-MIT+License] + [lib/bundler/vendor/connection_pool] connection_pool is under the following license. @@ -20,40 +20,563 @@ Note that each entry is kept to a minimum, see links for details. end ``` -## Command line options +* A proc that accepts a single positional argument and keywords will + no longer autosplat. [[Bug #18633]] + + ```ruby + proc{|a, **k| a}.call([1, 2]) + # Ruby 3.1 and before + # => 1 + # Ruby 3.2 and after + # => [1, 2] + ``` + +* Constant assignment evaluation order for constants set on explicit + objects has been made consistent with single attribute assignment + evaluation order. With this code: + + ```ruby + foo::BAR = baz + ``` + + `foo` is now called before `baz`. Similarly, for multiple assignments + to constants, left-to-right evaluation order is used. With this + code: + + ```ruby + foo1::BAR1, foo2::BAR2 = baz1, baz2 + ``` + + The following evaluation order is now used: + + 1. `foo1` + 2. `foo2` + 3. `baz1` + 4. `baz2` + + [[Bug #15928]] + +* "Find pattern" is no longer experimental. + [[Feature #18585]] + +* Methods taking a rest parameter (like `*args`) and wishing to delegate keyword + arguments through `foo(*args)` must now be marked with `ruby2_keywords` + (if not already the case). In other words, all methods wishing to delegate + keyword arguments through `*args` must now be marked with `ruby2_keywords`, + with no exception. This will make it easier to transition to other ways of + delegation once a library can require Ruby 3+. Previously, the `ruby2_keywords` + flag was kept if the receiving method took `*args`, but this was a bug and an + inconsistency. A good technique to find the potentially-missing `ruby2_keywords` + is to run the test suite, for where it fails find the last method which must + receive keyword arguments, use `puts nil, caller, nil` there, and check each + method/block on the call chain which must delegate keywords is correctly marked + as `ruby2_keywords`. [[Bug #18625]] [[Bug #16466]] + + ```ruby + def target(**kw) + end + + # Accidentally worked without ruby2_keywords in Ruby 2.7-3.1, ruby2_keywords + # needed in 3.2+. Just like (*args, **kwargs) or (...) would be needed on + # both #foo and #bar when migrating away from ruby2_keywords. + ruby2_keywords def bar(*args) + target(*args) + end + + ruby2_keywords def foo(*args) + bar(*args) + end + + foo(k: 1) + ``` ## Core classes updates Note: We're only listing outstanding class updates. +* Fiber + + * Introduce Fiber.[] and Fiber.[]= for inheritable fiber storage. + Introduce Fiber#storage and Fiber#storage= (experimental) for + getting and resetting the current storage. Introduce + `Fiber.new(storage:)` for setting the storage when creating a + fiber. [[Feature #19078]] + + Existing Thread and Fiber local variables can be tricky to use. + Thread-local variables are shared between all fibers, making it + hard to isolate, while Fiber-local variables can be hard to + share. It is often desirable to define unit of execution + ("execution context") such that some state is shared between all + fibers and threads created in that context. This is what Fiber + storage provides. + + ```ruby + def log(message) + puts "#{Fiber[:request_id]}: #{message}" + end + + def handle_requests + while request = read_request + Fiber.schedule do + Fiber[:request_id] = SecureRandom.uuid + + request.messages.each do |message| + Fiber.schedule do + log("Handling #{message}") # Log includes inherited request_id. + end + end + end + end + end + ``` + + You should generally consider Fiber storage for any state which + you want to be shared implicitly between all fibers and threads + created in a given context, e.g. a connection pool, a request + id, a logger level, environment variables, configuration, etc. + +* Fiber::Scheduler + + * Introduce `Fiber::Scheduler#io_select` for non-blocking IO.select. + [[Feature #19060]] + +* IO + + * Introduce IO#timeout= and IO#timeout which can cause + IO::TimeoutError to be raised if a blocking operation exceeds the + specified timeout. [[Feature #18630]] + + ```ruby + STDIN.timeout = 1 + STDIN.read # => Blocking operation timed out! (IO::TimeoutError) + ``` + + * Introduce `IO.new(..., path:)` and promote `File#path` to `IO#path`. + [[Feature #19036]] + +* Class + + * Class#attached_object, which returns the object for which + the receiver is the singleton class. Raises TypeError if the + receiver is not a singleton class. + [[Feature #12084]] + + ```ruby + class Foo; end + + Foo.singleton_class.attached_object #=> Foo + Foo.new.singleton_class.attached_object #=> #<Foo:0x000000010491a370> + Foo.attached_object #=> TypeError: `Foo' is not a singleton class + nil.singleton_class.attached_object #=> TypeError: `NilClass' is not a singleton class + ``` + +* Data + + * New core class to represent simple immutable value object. The class is + similar to Struct and partially shares an implementation, but has more + lean and strict API. [[Feature #16122]] + + ```ruby + Measure = Data.define(:amount, :unit) + distance = Measure.new(100, 'km') #=> #<data Measure amount=100, unit="km"> + weight = Measure.new(amount: 50, unit: 'kg') #=> #<data Measure amount=50, unit="kg"> + weight.with(amount: 40) #=> #<data Measure amount=40, unit="kg"> + weight.amount #=> 50 + weight.amount = 40 #=> NoMethodError: undefined method `amount=' + ``` + +* Encoding + + * Encoding#replicate has been deprecated and will be removed in 3.3. [[Feature #18949]] + * The dummy `Encoding::UTF_16` and `Encoding::UTF_32` encodings no longer + try to dynamically guess the endian based on a byte order mark. + Use `Encoding::UTF_16BE`/`UTF_16LE` and `Encoding::UTF_32BE`/`UTF_32LE` instead. + This change speeds up getting the encoding of a String. [[Feature #18949]] + * Limit maximum encoding set size by 256. + If exceeding maximum size, `EncodingError` will be raised. [[Feature #18949]] + +* Enumerator + + * Enumerator.product has been added. Enumerator::Product is the implementation. [[Feature #18685]] + +* Exception + + * Exception#detailed_message has been added. + The default error printer calls this method on the Exception object + instead of #message. [[Feature #18564]] + +* Hash + + * Hash#shift now always returns nil if the hash is + empty, instead of returning the default value or + calling the default proc. [[Bug #16908]] + +* Integer + + * Integer#ceildiv has been added. [[Feature #18809]] + +* Kernel + + * Kernel#binding raises RuntimeError if called from a non-Ruby frame + (such as a method defined in C). [[Bug #18487]] + +* MatchData + + * MatchData#byteoffset has been added. [[Feature #13110]] + * MatchData#deconstruct has been added. [[Feature #18821]] + * MatchData#deconstruct_keys has been added. [[Feature #18821]] + * Module + * Module.used_refinements has been added. [[Feature #14332]] * Module#refinements has been added. [[Feature #12737]] + * Module#const_added has been added. [[Feature #17881]] + * Module#undefined_instance_methods has been added. [[Feature #12655]] + +* Proc + + * Proc#dup returns an instance of subclass. [[Bug #17545]] + * Proc#parameters now accepts lambda keyword. [[Feature #15357]] + +* Process + * Added `RLIMIT_NPTS` constant to FreeBSD platform + +* Regexp + + * The cache-based optimization is introduced. + Many (but not all) Regexp matching is now in linear time, which + will prevent regular expression denial of service (ReDoS) + vulnerability. [[Feature #19104]] + + * Regexp.linear_time? is introduced. [[Feature #19194]] + + * Regexp.new now supports passing the regexp flags not only as an Integer, + but also as a String. Unknown flags raise ArgumentError. + Otherwise, anything other than `true`, `false`, `nil` or Integer will be warned. + [[Feature #18788]] + + * Regexp.timeout= has been added. Also, Regexp.new new supports timeout keyword. + See [[Feature #17837]] * Refinement + * Refinement#refined_class has been added. [[Feature #12737]] +* RubyVM::AbstractSyntaxTree + + * Add `error_tolerant` option for `parse`, `parse_file` and `of`. [[Feature #19013]] + With this option + + 1. SyntaxError is suppressed + 2. AST is returned for invalid input + 3. `end` is complemented when a parser reaches to the end of input but `end` is insufficient + 4. `end` is treated as keyword based on indent + + ```ruby + # Without error_tolerant option + root = RubyVM::AbstractSyntaxTree.parse(<<~RUBY) + def m + a = 10 + if + end + RUBY + # => <internal:ast>:33:in `parse': syntax error, unexpected `end' (SyntaxError) + + # With error_tolerant option + root = RubyVM::AbstractSyntaxTree.parse(<<~RUBY, error_tolerant: true) + def m + a = 10 + if + end + RUBY + p root # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-4:3> + + # `end` is treated as keyword based on indent + root = RubyVM::AbstractSyntaxTree.parse(<<~RUBY, error_tolerant: true) + module Z + class Foo + foo. + end + + def bar + end + end + RUBY + p root.children[-1].children[-1].children[-1].children[-2..-1] + # => [#<RubyVM::AbstractSyntaxTree::Node:CLASS@2:2-4:5>, #<RubyVM::AbstractSyntaxTree::Node:DEFN@6:2-7:5>] + ``` + + * Add `keep_tokens` option for `parse`, `parse_file` and `of`. Add `#tokens` and `#all_tokens` + for RubyVM::AbstractSyntaxTree::Node [[Feature #19070]] + + ```ruby + root = RubyVM::AbstractSyntaxTree.parse("x = 1 + 2", keep_tokens: true) + root.tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...] + root.tokens.map{_1[2]}.join # => "x = 1 + 2" + ``` + +* Set + + * Set is now available as a built-in class without the need for `require "set"`. [[Feature #16989]] + It is currently autoloaded via the Set constant or a call to Enumerable#to_set. + +* String + + * String#byteindex and String#byterindex have been added. [[Feature #13110]] + * Update Unicode to Version 15.0.0 and Emoji Version 15.0. [[Feature #18639]] + (also applies to Regexp) + * String#bytesplice has been added. [[Feature #18598]] + * String#dedup has been added as an alias to String#-@. [[Feature #18595]] + +* Struct + + * A Struct class can also be initialized with keyword arguments + without `keyword_init: true` on Struct.new [[Feature #16806]] + + ```ruby + Post = Struct.new(:id, :name) + Post.new(1, "hello") #=> #<struct Post id=1, name="hello"> + # From Ruby 3.2, the following code also works without keyword_init: true. + Post.new(id: 1, name: "hello") #=> #<struct Post id=1, name="hello"> + ``` + +* Thread + + * Thread.each_caller_location is added. [[Feature #16663]] + +* Thread::Queue + + * Thread::Queue#pop(timeout: sec) is added. [[Feature #18774]] + +* Thread::SizedQueue + + * Thread::SizedQueue#pop(timeout: sec) is added. [[Feature #18774]] + * Thread::SizedQueue#push(timeout: sec) is added. [[Feature #18944]] + +* Time + + * Time#deconstruct_keys is added, allowing to use Time instances + in pattern-matching expressions [[Feature #19071]] + + * Time.new now can parse a string like generated by Time#inspect + and return a Time instance based on the given argument. + [[Feature #18033]] + +* SyntaxError + * SyntaxError#path has been added. [[Feature #19138]] + +* TracePoint + + * TracePoint#binding now returns `nil` for `c_call`/`c_return` TracePoints. + [[Bug #18487]] + * TracePoint#enable `target_thread` keyword argument now defaults to the + current thread if a block is given and `target` and `target_line` keyword + arguments are not passed. [[Bug #16889]] + +* UnboundMethod + + * `UnboundMethod#==` returns `true` if the actual method is same. For example, + `String.instance_method(:object_id) == Array.instance_method(:object_id)` + returns `true`. [[Feature #18798]] + + * `UnboundMethod#inspect` does not show the receiver of `instance_method`. + For example `String.instance_method(:object_id).inspect` returns + `"#<UnboundMethod: Kernel#object_id()>"` + (was `"#<UnboundMethod: String(Kernel)#object_id()>"`). + +* GC + + * Expose `need_major_gc` via `GC.latest_gc_info`. [GH-6791] + +* ObjectSpace + + * `ObjectSpace.dump_all` dump shapes as well. [GH-6868] + ## Stdlib updates -* The following default gem are updated. - * RubyGems 3.4.0.dev - * bundler 2.4.0.dev - * etc 1.4.0 - * io-console 0.5.11 - * reline 0.3.1 +* Bundler + + * Bundler now uses [PubGrub] resolver instead of [Molinillo] for performance improvement. + * Add --ext=rust support to bundle gem for creating simple gems with Rust extensions. + [[GH-rubygems-6149]] + * Make cloning git repos faster [[GH-rubygems-4475]] + +* RubyGems + + * Add mswin support for cargo builder. [[GH-rubygems-6167]] + +* CGI + + * `CGI.escapeURIComponent` and `CGI.unescapeURIComponent` are added. + [[Feature #18822]] + +* Coverage + + * `Coverage.setup` now accepts `eval: true`. By this, `eval` and related methods are + able to generate code coverage. [[Feature #19008]] + + * `Coverage.supported?(mode)` enables detection of what coverage modes are + supported. [[Feature #19026]] + +* Date + + * Added `Date#deconstruct_keys` and `DateTime#deconstruct_keys` same as [[Feature #19071]] + +* ERB + + * `ERB::Util.html_escape` is made faster than `CGI.escapeHTML`. + * It no longer allocates a String object when no character needs to be escaped. + * It skips calling `#to_s` method when an argument is already a String. + * `ERB::Escape.html_escape` is added as an alias to `ERB::Util.html_escape`, + which has not been monkey-patched by Rails. + * `ERB::Util.url_encode` is made faster using `CGI.escapeURIComponent`. + * `-S` option is removed from `erb` command. + +* FileUtils + + * Add FileUtils.ln_sr method and `relative:` option to FileUtils.ln_s. + [[Feature #18925]] + +* IRB + + * debug.gem integration commands have been added: `debug`, `break`, `catch`, + `next`, `delete`, `step`, `continue`, `finish`, `backtrace`, `info` + * They work even if you don't have `gem "debug"` in your Gemfile. + * See also: [What's new in Ruby 3.2's IRB?](https://st0012.dev/whats-new-in-ruby-3-2-irb) + * More Pry-like commands and features have been added. + * `edit` and `show_cmds` (like Pry's `help`) are added. + * `ls` takes `-g` or `-G` option to filter out outputs. + * `show_source` is aliased from `$` and accepts unquoted inputs. + * `whereami` is aliased from `@`. + +* Net::Protocol + + * Improve `Net::BufferedIO` performance. [[GH-net-protocol-14]] + +* Pathname + + * Added `Pathname#lutime`. [[GH-pathname-20]] + +* Socket + + * Added the following constants for supported platforms. + * `SO_INCOMING_CPU` + * `SO_INCOMING_NAPI_ID` + * `SO_RTABLE` + * `SO_SETFIB` + * `SO_USER_COOKIE` + * `TCP_KEEPALIVE` + * `TCP_CONNECTION_INFO` + +* SyntaxSuggest + + * The feature of `syntax_suggest` formerly `dead_end` is integrated in Ruby. + [[Feature #18159]] + +* UNIXSocket + + * Add support for UNIXSocket on Windows. Emulate anonymous sockets. Add + support for File.socket? and File::Stat#socket? where possible. + [[Feature #19135]] + +* The following default gems are updated. + + * RubyGems 3.4.1 + * abbrev 0.1.1 + * benchmark 0.2.1 + * bigdecimal 3.1.3 + * bundler 2.4.1 + * cgi 0.3.6 + * csv 3.2.6 + * date 3.3.3 + * delegate 0.3.0 + * did_you_mean 1.6.3 + * digest 3.1.1 + * drb 2.1.1 + * english 0.7.2 + * erb 4.0.2 + * error_highlight 0.5.1 + * etc 1.4.2 + * fcntl 1.0.2 + * fiddle 1.1.1 + * fileutils 1.7.0 + * forwardable 1.3.3 + * getoptlong 0.2.0 + * io-console 0.6.0 + * io-nonblock 0.2.0 + * io-wait 0.3.0 + * ipaddr 1.2.5 + * irb 1.6.2 + * json 2.6.3 + * logger 1.5.3 + * mutex_m 0.1.2 + * net-http 0.4.0 + * net-protocol 0.2.1 + * nkf 0.1.2 + * open-uri 0.3.0 + * open3 0.1.2 + * openssl 3.1.0 + * optparse 0.3.1 + * ostruct 0.5.5 + * pathname 0.2.1 + * pp 0.4.0 + * pstore 0.1.2 + * psych 5.0.1 + * racc 1.6.2 + * rdoc 6.5.0 + * readline-ext 0.1.5 + * reline 0.3.2 + * resolv 0.2.2 + * resolv-replace 0.1.1 + * securerandom 0.2.2 + * set 1.0.3 + * stringio 3.0.4 + * strscan 3.0.5 + * syntax_suggest 1.0.2 + * syslog 0.1.1 + * tempfile 0.1.3 + * time 0.2.1 + * timeout 0.3.1 + * tmpdir 0.1.3 + * tsort 0.1.1 + * un 0.2.1 + * uri 0.12.0 + * weakref 0.1.2 + * win32ole 1.8.9 + * yaml 0.2.1 + * zlib 3.0.0 + * The following bundled gems are updated. - * net-imap 0.2.3 - * typeprof 0.21.2 -* The following default gems are now bundled gems. + + * minitest 5.16.3 + * power_assert 2.0.3 + * test-unit 3.5.7 + * net-ftp 0.2.0 + * net-imap 0.3.4 + * net-pop 0.1.2 + * net-smtp 0.3.3 + * rbs 2.8.2 + * typeprof 0.21.3 + * debug 1.7.1 + +See GitHub releases like [GitHub Releases of Logger](https://github.com/ruby/logger/releases) or changelog for details of the default gems or bundled gems. + +## Supported platforms + +* WebAssembly/WASI is added. See [wasm/README.md] and [ruby.wasm] for more details. [[Feature #18462]] ## Compatibility issues -Note: Excluding feature bug fixes. +* `String#to_c` currently treat a sequence of underscores as an end of Complex + string. [[Bug #19087]] + +* Now `ENV.clone` raises `TypeError` as well as `ENV.dup` [[Bug #17767]] ### Removed constants The following deprecated constants are removed. +* `Fixnum` and `Bignum` [[Feature #12005]] * `Random::DEFAULT` [[Feature #17351]] * `Struct::Group` * `Struct::Passwd` @@ -69,44 +592,229 @@ The following deprecated methods are removed. [[Feature #16131]] * `Kernel#trust`, `Kernel#untrust`, `Kernel#untrusted?` [[Feature #16131]] +* `Method#public?`, `Method#private?`, `Method#protected?`, + `UnboundMethod#public?`, `UnboundMethod#private?`, `UnboundMethod#protected?` + [[Bug #18729]] [[Bug #18751]] [[Bug #18435]] + +### Source code incompatibility of extension libraries + +* Extension libraries provide PRNG, subclasses of Random, need updates. + See [PRNG update] below for more information. [[Bug #19100]] + +### Error printer + +* Ruby no longer escapes control characters and backslashes in an + error message. [[Feature #18367]] + +### Constant lookup when defining a class/module + +* When defining a class/module directly under the Object class by class/module + statement, if there is already a class/module defined by `Module#include` + with the same name, the statement was handled as "open class" in Ruby 3.1 or before. + Since Ruby 3.2, a new class is defined instead. [[Feature #18832]] ## Stdlib compatibility issues +* Psych no longer bundles libyaml sources. + And also Fiddle no longer bundles libffi sources. + Users need to install the libyaml/libffi library themselves via the package + manager like apt, yum, brew, etc. + + Psych and fiddle supported the static build with specific version of libyaml + and libffi sources. You can build psych with libyaml-0.2.5 like this. + + ```bash + $ ./configure --with-libyaml-source-dir=/path/to/libyaml-0.2.5 + ``` + + And you can build fiddle with libffi-3.4.4 like this. + + ```bash + $ ./configure --with-libffi-source-dir=/path/to/libffi-3.4.4 + ``` + + [[Feature #18571]] + +* Check cookie name/path/domain characters in `CGI::Cookie`. [[CVE-2021-33621]] + +* `URI.parse` return empty string in host instead of nil. [[sec-156615]] + ## C API updates -### Removed C APIs +### Updated C APIs -The following deprecated APIs are removed. +The following APIs are updated. -* `rb_cData` variable. -* "taintedness" and "trustedness" functions. [[Feature #16131]] +* PRNG update -## Implementation improvements + `rb_random_interface_t` in ruby/random.h updated and versioned. + Extension libraries which use this interface and built for older + versions need to rebuild with adding `init_int32` function. -## JIT +### Added C APIs -### MJIT +* `VALUE rb_hash_new_capa(long capa)` was added to created hashes with the desired capacity. +* `rb_internal_thread_add_event_hook` and `rb_internal_thread_add_event_hook` were added to instrument threads scheduling. + The following events are available: + * `RUBY_INTERNAL_THREAD_EVENT_STARTED` + * `RUBY_INTERNAL_THREAD_EVENT_READY` + * `RUBY_INTERNAL_THREAD_EVENT_RESUMED` + * `RUBY_INTERNAL_THREAD_EVENT_SUSPENDED` + * `RUBY_INTERNAL_THREAD_EVENT_EXITED` +* `rb_debug_inspector_current_depth` and `rb_debug_inspector_frame_depth` are added for debuggers. -### YJIT: New experimental in-process JIT compiler +### Removed C APIs -## Static analysis +The following deprecated APIs are removed. -### RBS +* `rb_cData` variable. +* "taintedness" and "trustedness" functions. [[Feature #16131]] -### TypeProf +## Implementation improvements -## Debugger +* Fixed several race conditions in Kernel#autoload. [[Bug #18782]] +* Cache invalidation for expressions referencing constants is now + more fine-grained. `RubyVM.stat(:global_constant_state)` was + removed because it was closely tied to the previous caching scheme + where setting any constant invalidates all caches in the system. + New keys, `:constant_cache_invalidations` and `:constant_cache_misses`, + were introduced to help with use cases for `:global_constant_state`. + [[Feature #18589]] +* The cache-based optimization for Regexp matching is introduced. + [[Feature #19104]] +* [Variable Width Allocation](https://shopify.engineering/ruby-variable-width-allocation) + is now enabled by default. [[Feature #18239]] +* Added a new instance variable caching mechanism, called object shapes, which + improves inline cache hits for most objects and allows us to generate very + efficient JIT code. Objects whose instance variables are defined in a + consistent order will see the most performance benefits. + [[Feature #18776]] +* Speed up marking instruction sequences by using a bitmap to find "markable" + objects. This change results in faster major collections. + [[Feature #18875]] -## error_highlight +## JIT -## IRB Autocomplete and Document Display +### YJIT + +* YJIT is no longer experimental + * Has been tested on production workloads for over a year and proven to be quite stable. +* YJIT now supports both x86-64 and arm64/aarch64 CPUs on Linux, MacOS, BSD and other UNIX platforms. + * This release brings support for Mac M1/M2, AWS Graviton and Raspberry Pi 4. +* Building YJIT now requires Rust 1.58.0+. [[Feature #18481]] + * In order to ensure that CRuby is built with YJIT, please install `rustc` >= 1.58.0 + before running `./configure` + * Please reach out to the YJIT team should you run into any issues. +* Physical memory for JIT code is lazily allocated. Unlike Ruby 3.1, + the RSS of a Ruby process is minimized because virtual memory pages + allocated by `--yjit-exec-mem-size` will not be mapped to physical + memory pages until actually utilized by JIT code. +* Introduce Code GC that frees all code pages when the memory consumption + by JIT code reaches `--yjit-exec-mem-size`. + * `RubyVM::YJIT.runtime_stats` returns Code GC metrics in addition to + existing `inline_code_size` and `outlined_code_size` keys: + `code_gc_count`, `live_page_count`, `freed_page_count`, and `freed_code_size`. +* Most of the statistics produced by `RubyVM::YJIT.runtime_stats` are now available in release builds. + * Simply run ruby with `--yjit-stats` to compute and dump stats (incurs some run-time overhead). +* YJIT is now optimized to take advantage of object shapes. [[Feature #18776]] +* Take advantage of finer-grained constant invalidation to invalidate less code when defining new constants. [[Feature #18589]] +* The default `--yjit-exec-mem-size` is changed to 64 (MiB). +* The default `--yjit-call-threshold` is changed to 30. -## Miscellaneous changes +### MJIT -[Feature #12737]: https://bugs.ruby-lang.org/issues/12737 -[Feature #14332]: https://bugs.ruby-lang.org/issues/14332 -[Feature #15231]: https://bugs.ruby-lang.org/issues/15231 -[Feature #16131]: https://bugs.ruby-lang.org/issues/16131 -[Feature #17351]: https://bugs.ruby-lang.org/issues/17351 -[Feature #17391]: https://bugs.ruby-lang.org/issues/17391 -[Feature #18351]: https://bugs.ruby-lang.org/issues/18351 +* The MJIT compiler is re-implemented in Ruby as `ruby_vm/mjit/compiler`. +* MJIT compiler is executed under a forked Ruby process instead of + doing it in a native thread called MJIT worker. [[Feature #18968]] + * As a result, Microsoft Visual Studio (MSWIN) is no longer supported. +* MinGW is no longer supported. [[Feature #18824]] +* Rename `--mjit-min-calls` to `--mjit-call-threshold`. +* Change default `--mjit-max-cache` back from 10000 to 100. + +[Feature #12005]: https://bugs.ruby-lang.org/issues/12005 +[Feature #12084]: https://bugs.ruby-lang.org/issues/12084 +[Feature #12655]: https://bugs.ruby-lang.org/issues/12655 +[Feature #12737]: https://bugs.ruby-lang.org/issues/12737 +[Feature #13110]: https://bugs.ruby-lang.org/issues/13110 +[Feature #14332]: https://bugs.ruby-lang.org/issues/14332 +[Feature #15231]: https://bugs.ruby-lang.org/issues/15231 +[Feature #15357]: https://bugs.ruby-lang.org/issues/15357 +[Bug #15928]: https://bugs.ruby-lang.org/issues/15928 +[Feature #16122]: https://bugs.ruby-lang.org/issues/16122 +[Feature #16131]: https://bugs.ruby-lang.org/issues/16131 +[Bug #16466]: https://bugs.ruby-lang.org/issues/16466 +[Feature #16663]: https://bugs.ruby-lang.org/issues/16663 +[Feature #16806]: https://bugs.ruby-lang.org/issues/16806 +[Bug #16889]: https://bugs.ruby-lang.org/issues/16889 +[Bug #16908]: https://bugs.ruby-lang.org/issues/16908 +[Feature #16989]: https://bugs.ruby-lang.org/issues/16989 +[Feature #17351]: https://bugs.ruby-lang.org/issues/17351 +[Feature #17391]: https://bugs.ruby-lang.org/issues/17391 +[Bug #17545]: https://bugs.ruby-lang.org/issues/17545 +[Bug #17767]: https://bugs.ruby-lang.org/issues/17767 +[Feature #17837]: https://bugs.ruby-lang.org/issues/17837 +[Feature #17881]: https://bugs.ruby-lang.org/issues/17881 +[Feature #18033]: https://bugs.ruby-lang.org/issues/18033 +[Feature #18159]: https://bugs.ruby-lang.org/issues/18159 +[Feature #18239]: https://bugs.ruby-lang.org/issues/18239#note-17 +[Feature #18351]: https://bugs.ruby-lang.org/issues/18351 +[Feature #18367]: https://bugs.ruby-lang.org/issues/18367 +[Bug #18435]: https://bugs.ruby-lang.org/issues/18435 +[Feature #18462]: https://bugs.ruby-lang.org/issues/18462 +[Feature #18481]: https://bugs.ruby-lang.org/issues/18481 +[Bug #18487]: https://bugs.ruby-lang.org/issues/18487 +[Feature #18564]: https://bugs.ruby-lang.org/issues/18564 +[Feature #18571]: https://bugs.ruby-lang.org/issues/18571 +[Feature #18585]: https://bugs.ruby-lang.org/issues/18585 +[Feature #18589]: https://bugs.ruby-lang.org/issues/18589 +[Feature #18595]: https://bugs.ruby-lang.org/issues/18595 +[Feature #18598]: https://bugs.ruby-lang.org/issues/18598 +[Bug #18625]: https://bugs.ruby-lang.org/issues/18625 +[Feature #18630]: https://bugs.ruby-lang.org/issues/18630 +[Bug #18633]: https://bugs.ruby-lang.org/issues/18633 +[Feature #18639]: https://bugs.ruby-lang.org/issues/18639 +[Feature #18685]: https://bugs.ruby-lang.org/issues/18685 +[Bug #18729]: https://bugs.ruby-lang.org/issues/18729 +[Bug #18751]: https://bugs.ruby-lang.org/issues/18751 +[Feature #18774]: https://bugs.ruby-lang.org/issues/18774 +[Feature #18776]: https://bugs.ruby-lang.org/issues/18776 +[Bug #18782]: https://bugs.ruby-lang.org/issues/18782 +[Feature #18788]: https://bugs.ruby-lang.org/issues/18788 +[Feature #18798]: https://bugs.ruby-lang.org/issues/18798 +[Feature #18809]: https://bugs.ruby-lang.org/issues/18809 +[Feature #18821]: https://bugs.ruby-lang.org/issues/18821 +[Feature #18822]: https://bugs.ruby-lang.org/issues/18822 +[Feature #18824]: https://bugs.ruby-lang.org/issues/18824 +[Feature #18832]: https://bugs.ruby-lang.org/issues/18832 +[Feature #18875]: https://bugs.ruby-lang.org/issues/18875 +[Feature #18925]: https://bugs.ruby-lang.org/issues/18925 +[Feature #18944]: https://bugs.ruby-lang.org/issues/18944 +[Feature #18949]: https://bugs.ruby-lang.org/issues/18949 +[Feature #18968]: https://bugs.ruby-lang.org/issues/18968 +[Feature #19008]: https://bugs.ruby-lang.org/issues/19008 +[Feature #19013]: https://bugs.ruby-lang.org/issues/19013 +[Feature #19026]: https://bugs.ruby-lang.org/issues/19026 +[Feature #19036]: https://bugs.ruby-lang.org/issues/19036 +[Feature #19060]: https://bugs.ruby-lang.org/issues/19060 +[Feature #19070]: https://bugs.ruby-lang.org/issues/19070 +[Feature #19071]: https://bugs.ruby-lang.org/issues/19071 +[Feature #19078]: https://bugs.ruby-lang.org/issues/19078 +[Bug #19087]: https://bugs.ruby-lang.org/issues/19087 +[Bug #19100]: https://bugs.ruby-lang.org/issues/19100 +[Feature #19104]: https://bugs.ruby-lang.org/issues/19104 +[Feature #19135]: https://bugs.ruby-lang.org/issues/19135 +[Feature #19138]: https://bugs.ruby-lang.org/issues/19138 +[Feature #19194]: https://bugs.ruby-lang.org/issues/19194 +[Molinillo]: https://github.com/CocoaPods/Molinillo +[PubGrub]: https://github.com/jhawthorn/pub_grub +[GH-net-protocol-14]: https://github.com/ruby/net-protocol/pull/14 +[GH-pathname-20]: https://github.com/ruby/pathname/pull/20 +[GH-6791]: https://github.com/ruby/ruby/pull/6791 +[GH-6868]: https://github.com/ruby/ruby/pull/6868 +[GH-rubygems-4475]: https://github.com/rubygems/rubygems/pull/4475 +[GH-rubygems-6149]: https://github.com/rubygems/rubygems/pull/6149 +[GH-rubygems-6167]: https://github.com/rubygems/rubygems/pull/6167 +[sec-156615]: https://hackerone.com/reports/156615 +[CVE-2021-33621]: https://www.ruby-lang.org/en/news/2022/11/22/http-response-splitting-in-cgi-cve-2021-33621/ +[wasm/README.md]: https://github.com/ruby/ruby/blob/master/wasm/README.md +[ruby.wasm]: https://github.com/ruby/ruby.wasm diff --git a/README.ja.md b/README.ja.md index bb69c09055..93c0131690 100644 --- a/README.ja.md +++ b/README.ja.md @@ -4,7 +4,6 @@ [](https://github.com/ruby/ruby/actions?query=workflow%3A"Windows") [](https://ci.appveyor.com/project/ruby/ruby/branch/master) [](https://app.travis-ci.com/ruby/ruby) -[](https://cirrus-ci.com/github/ruby/ruby/master) # Rubyとは @@ -4,9 +4,8 @@ [](https://github.com/ruby/ruby/actions?query=workflow%3A"Windows") [](https://ci.appveyor.com/project/ruby/ruby/branch/master) [](https://app.travis-ci.com/ruby/ruby) -[](https://cirrus-ci.com/github/ruby/ruby/master) -# What's Ruby +# What is Ruby? Ruby is an interpreted object-oriented programming language often used for web development. It also offers many scripting features @@ -15,28 +14,25 @@ It is simple, straightforward, and extensible. ## Features of Ruby -* Simple Syntax -* **Normal** Object-oriented Features (e.g. class, method calls) -* **Advanced** Object-oriented Features (e.g. mix-in, singleton-method) -* Operator Overloading -* Exception Handling -* Iterators and Closures -* Garbage Collection -* Dynamic Loading of Object Files (on some architectures) -* Highly Portable (works on many Unix-like/POSIX compatible platforms as - well as Windows, macOS, etc.) cf. - https://github.com/ruby/ruby/blob/master/doc/maintainers.rdoc#label-Platform+Maintainers +* Simple Syntax +* **Normal** Object-oriented Features (e.g. class, method calls) +* **Advanced** Object-oriented Features (e.g. mix-in, singleton-method) +* Operator Overloading +* Exception Handling +* Iterators and Closures +* Garbage Collection +* Dynamic Loading of Object Files (on some architectures) +* Highly Portable (works on many Unix-like/POSIX compatible platforms as + well as Windows, macOS, etc.) cf. + https://github.com/ruby/ruby/blob/master/doc/maintainers.rdoc#label-Platform+Maintainers - -## How to get Ruby +## How to get Ruby with Git For a complete list of ways to install Ruby, including using third-party tools like rvm, see: https://www.ruby-lang.org/en/downloads/ -### Git - The mirror of the Ruby source tree can be checked out with the following command: $ git clone https://github.com/ruby/ruby.git @@ -49,22 +45,19 @@ to see the list of branches: You may also want to use https://git.ruby-lang.org/ruby.git (actual master of Ruby source) if you are a committer. -### Subversion - -Stable branches for older Ruby versions can be checked out with also the -following command: - - $ svn co https://svn.ruby-lang.org/repos/ruby/branches/ruby_2_6/ ruby - -Try the following command to see the list of branches: - - $ svn ls https://svn.ruby-lang.org/repos/ruby/branches/ +## How to build +see [Building Ruby](doc/contributing/building_ruby.md) ## Ruby home page https://www.ruby-lang.org/ +## Documentation + +- [English](https://docs.ruby-lang.org/en/master/index.html) +- [Japanese](https://docs.ruby-lang.org/ja/master/index.html) + ## Mailing list There is a mailing list to discuss Ruby. To subscribe to this list, please @@ -76,108 +69,20 @@ in the mail body (not subject) to the address [ruby-talk-request@ruby-lang.org]. [ruby-talk-request@ruby-lang.org]: mailto:ruby-talk-request@ruby-lang.org?subject=Join%20Ruby%20Mailing%20List&body=subscribe -## Requirements to build from repository - -1. GNU or BSD make -2. C99 compiler -3. autoconf 2.67 or higher -4. automake 1.15 or higher -5. bison 2.3 or higher -6. Ruby 2.2 or higher - -When building from a released version, only a C99 compiler and GNU or BSD make -is required. - -## How to compile and install - -1. If you want to use Microsoft Visual C++ to compile Ruby, read - [win32/README.win32](rdoc-ref:win32/README.win32) instead of this document. - -2. Run `./autogen.sh` to generate configure, when you build the source checked - out from the Git repository. - -3. Run `./configure`, which will generate `config.h` and `Makefile`. - - Some C compiler flags may be added by default depending on your - environment. Specify `optflags=..` and `warnflags=..` as necessary to - override them. - -4. Edit `include/ruby/defines.h` if you need. Usually this step will not be needed. - -5. Optional: Remove comment mark(`#`) before the module names from `ext/Setup`. - - This step is only necessary if you want to link modules statically. - - If you don't want to compile dynamic extensions (probably on architectures - which do not allow dynamic loading), remove comment mark from the line - "`#option nodynamic`" in `ext/Setup`. - - Usually this step will not be needed. - -6. Run `make`. - - * On Mac, set RUBY\_CODESIGN environment variable with a signing identity. - It uses the identity to sign `ruby` binary. See also codesign(1). - -7. Optionally, run '`make check`' to check whether the compiled Ruby - interpreter works well. If you see the message "`check succeeded`", your - Ruby works as it should (hopefully). - -8. Run '`make install`'. - - This command will create the following directories and install files into - them. - - * `${DESTDIR}${prefix}/bin` - * `${DESTDIR}${prefix}/include/ruby-${MAJOR}.${MINOR}.${TEENY}` - * `${DESTDIR}${prefix}/include/ruby-${MAJOR}.${MINOR}.${TEENY}/${PLATFORM}` - * `${DESTDIR}${prefix}/lib` - * `${DESTDIR}${prefix}/lib/ruby` - * `${DESTDIR}${prefix}/lib/ruby/${MAJOR}.${MINOR}.${TEENY}` - * `${DESTDIR}${prefix}/lib/ruby/${MAJOR}.${MINOR}.${TEENY}/${PLATFORM}` - * `${DESTDIR}${prefix}/lib/ruby/site_ruby` - * `${DESTDIR}${prefix}/lib/ruby/site_ruby/${MAJOR}.${MINOR}.${TEENY}` - * `${DESTDIR}${prefix}/lib/ruby/site_ruby/${MAJOR}.${MINOR}.${TEENY}/${PLATFORM}` - * `${DESTDIR}${prefix}/lib/ruby/vendor_ruby` - * `${DESTDIR}${prefix}/lib/ruby/vendor_ruby/${MAJOR}.${MINOR}.${TEENY}` - * `${DESTDIR}${prefix}/lib/ruby/vendor_ruby/${MAJOR}.${MINOR}.${TEENY}/${PLATFORM}` - * `${DESTDIR}${prefix}/lib/ruby/gems/${MAJOR}.${MINOR}.${TEENY}` - * `${DESTDIR}${prefix}/share/man/man1` - * `${DESTDIR}${prefix}/share/ri/${MAJOR}.${MINOR}.${TEENY}/system` - - - If Ruby's API version is '*x.y.z*', the `${MAJOR}` is '*x*', the - `${MINOR}` is '*y*', and the `${TEENY}` is '*z*'. - - **NOTE**: teeny of the API version may be different from one of Ruby's - program version - - You may have to be a super user to install Ruby. - -If you fail to compile Ruby, please send the detailed error report with the -error log and machine/OS type, to help others. - -Some extension libraries may not get compiled because of lack of necessary -external libraries and/or headers, then you will need to run '`make distclean-ext`' -to remove old configuration after installing them in such case. - ## Copying See the file [COPYING](rdoc-ref:COPYING). ## Feedback -Questions about the Ruby language can be asked on the [Ruby-Talk] mailing list +Questions about the Ruby language can be asked on the [Ruby-Talk](https://www.ruby-lang.org/en/community/mailing-lists) mailing list or on websites like https://stackoverflow.com. -Bugs should be reported at https://bugs.ruby-lang.org. Read [HowToReport] for more information. - -[Ruby-Talk]: https://www.ruby-lang.org/en/community/mailing-lists -[HowToReport]: https://bugs.ruby-lang.org/projects/ruby/wiki/HowToReport +Bugs should be reported at https://bugs.ruby-lang.org. Read ["Reporting Issues"](https://docs.ruby-lang.org/en/master/contributing/reporting_issues_md.html) for more information. ## Contributing -See the file [CONTRIBUTING.md](rdoc-ref:CONTRIBUTING) +See ["Contributing to Ruby"](https://docs.ruby-lang.org/en/master/contributing_md.html), which includes setup and build instructions. ## The Author diff --git a/addr2line.c b/addr2line.c index f660be9129..e5f25293e2 100644 --- a/addr2line.c +++ b/addr2line.c @@ -159,12 +159,15 @@ typedef struct obj_info { struct dwarf_section debug_info; struct dwarf_section debug_line; struct dwarf_section debug_ranges; + struct dwarf_section debug_str_offsets; + struct dwarf_section debug_addr; struct dwarf_section debug_rnglists; struct dwarf_section debug_str; + struct dwarf_section debug_line_str; struct obj_info *next; } obj_info_t; -#define DWARF_SECTION_COUNT 6 +#define DWARF_SECTION_COUNT 9 static struct dwarf_section * obj_dwarf_section_at(obj_info_t *obj, int n) @@ -174,8 +177,11 @@ obj_dwarf_section_at(obj_info_t *obj, int n) &obj->debug_info, &obj->debug_line, &obj->debug_ranges, + &obj->debug_str_offsets, + &obj->debug_addr, &obj->debug_rnglists, - &obj->debug_str + &obj->debug_str, + &obj->debug_line_str }; if (n < 0 || DWARF_SECTION_COUNT <= n) { abort(); @@ -248,39 +254,51 @@ get_nth_dirname(unsigned long dir, const char *p) return p; } +static const char *parse_ver5_debug_line_header(const char *p, int idx, uint8_t format, obj_info_t *obj, const char **out_path, uint64_t *out_directory_index); + static void -fill_filename(int file, const char *include_directories, const char *filenames, line_info_t *line, obj_info_t *obj) +fill_filename(int file, uint8_t format, uint16_t version, const char *include_directories, const char *filenames, line_info_t *line, obj_info_t *obj) { int i; const char *p = filenames; const char *filename; unsigned long dir; - for (i = 1; i <= file; i++) { - filename = p; - if (!*p) { - /* Need to output binary file name? */ - kprintf("Unexpected file number %d in %s at %tx\n", - file, binary_filename, filenames - obj->mapped); - return; - } - while (*p) p++; - p++; - dir = uleb128(&p); - /* last modified. */ - uleb128(&p); - /* size of the file. */ - uleb128(&p); - - if (i == file) { - line->filename = filename; - line->dirname = get_nth_dirname(dir, include_directories); - } + if (version >= 5) { + const char *path; + uint64_t directory_index = -1; + parse_ver5_debug_line_header(filenames, file, format, obj, &path, &directory_index); + line->filename = path; + parse_ver5_debug_line_header(include_directories, (int)directory_index, format, obj, &path, NULL); + line->dirname = path; + } + else { + for (i = 1; i <= file; i++) { + filename = p; + if (!*p) { + /* Need to output binary file name? */ + kprintf("Unexpected file number %d in %s at %tx\n", + file, binary_filename, filenames - obj->mapped); + return; + } + while (*p) p++; + p++; + dir = uleb128(&p); + /* last modified. */ + uleb128(&p); + /* size of the file. */ + uleb128(&p); + + if (i == file) { + line->filename = filename; + line->dirname = get_nth_dirname(dir, include_directories); + } + } } } static void fill_line(int num_traces, void **traces, uintptr_t addr, int file, int line, - const char *include_directories, const char *filenames, + uint8_t format, uint16_t version, const char *include_directories, const char *filenames, obj_info_t *obj, line_info_t *lines, int offset) { int i; @@ -290,7 +308,7 @@ fill_line(int num_traces, void **traces, uintptr_t addr, int file, int line, /* We assume one line code doesn't result >100 bytes of native code. We may want more reliable way eventually... */ if (addr < a && a < addr + 100) { - fill_filename(file, include_directories, filenames, &lines[i], obj); + fill_filename(file, format, version, include_directories, filenames, &lines[i], obj); lines[i].line = line; } } @@ -315,7 +333,7 @@ struct LineNumberProgramHeader { }; static int -parse_debug_line_header(const char **pp, struct LineNumberProgramHeader *header) +parse_debug_line_header(obj_info_t *obj, const char **pp, struct LineNumberProgramHeader *header) { const char *p = *pp; header->unit_length = *(uint32_t *)p; @@ -332,7 +350,13 @@ parse_debug_line_header(const char **pp, struct LineNumberProgramHeader *header) header->version = *(uint16_t *)p; p += sizeof(uint16_t); - if (header->version > 4) return -1; + if (header->version > 5) return -1; + + if (header->version >= 5) { + /* address_size = *(uint8_t *)p++; */ + /* segment_selector_size = *(uint8_t *)p++; */ + p += 2; + } header->header_length = header->format == 4 ? *(uint32_t *)p : *(uint64_t *)p; p += header->format; @@ -353,20 +377,27 @@ parse_debug_line_header(const char **pp, struct LineNumberProgramHeader *header) /* header->standard_opcode_lengths = (uint8_t *)p - 1; */ p += header->opcode_base - 1; - header->include_directories = p; + if (header->version >= 5) { + header->include_directories = p; + p = parse_ver5_debug_line_header(p, -1, header->format, obj, NULL, NULL); + header->filenames = p; + } + else { + header->include_directories = p; - /* temporary measure for compress-debug-sections */ - if (p >= header->cu_end) return -1; + /* temporary measure for compress-debug-sections */ + if (p >= header->cu_end) return -1; - /* skip include directories */ - while (*p) { - p = memchr(p, '\0', header->cu_end - p); - if (!p) return -1; - p++; - } - p++; + /* skip include directories */ + while (*p) { + p = memchr(p, '\0', header->cu_end - p); + if (!p) return -1; + p++; + } + p++; - header->filenames = p; + header->filenames = p; + } *pp = header->cu_start; @@ -392,13 +423,15 @@ parse_debug_line_cu(int num_traces, void **traces, const char **debug_line, /* int epilogue_begin = 0; */ /* unsigned int isa = 0; */ - if (parse_debug_line_header(&p, &header)) + if (parse_debug_line_header(obj, &p, &header)) return -1; is_stmt = header.default_is_stmt; #define FILL_LINE() \ do { \ fill_line(num_traces, traces, addr, file, line, \ + header.format, \ + header.version, \ header.include_directories, \ header.filenames, \ obj, lines, offset); \ @@ -827,16 +860,23 @@ enum { VAL_cstr = 1, VAL_data = 2, VAL_uint = 3, - VAL_int = 4 + VAL_int = 4, + VAL_addr = 5 }; # define ABBREV_TABLE_SIZE 256 typedef struct { obj_info_t *obj; const char *file; + uint8_t current_version; const char *current_cu; uint64_t current_low_pc; + uint64_t current_str_offsets_base; + uint64_t current_addr_base; + uint64_t current_rnglists_base; const char *debug_line_cu_end; + uint8_t debug_line_format; + uint16_t debug_line_version; const char *debug_line_files; const char *debug_line_directories; const char *p; @@ -861,6 +901,7 @@ typedef struct { const char *ptr; uint64_t uint64; int64_t int64; + uint64_t addr_idx; } as; uint64_t off; uint64_t at; @@ -869,8 +910,11 @@ typedef struct { int type; } DebugInfoValue; -/* TODO: Big Endian */ +#if defined(WORDS_BIGENDIAN) +#define MERGE_2INTS(a,b,sz) (((uint64_t)(a)<<sz)|(b)) +#else #define MERGE_2INTS(a,b,sz) (((uint64_t)(b)<<sz)|(a)) +#endif static uint16_t get_uint16(const uint8_t *p) @@ -973,6 +1017,9 @@ debug_info_reader_init(DebugInfoReader *reader, obj_info_t *obj) reader->pend = obj->debug_info.ptr + obj->debug_info.size; reader->debug_line_cu_end = obj->debug_line.ptr; reader->current_low_pc = 0; + reader->current_str_offsets_base = 0; + reader->current_addr_base = 0; + reader->current_rnglists_base = 0; } static void @@ -1017,10 +1064,12 @@ di_read_debug_line_cu(DebugInfoReader *reader) struct LineNumberProgramHeader header; p = (const char *)reader->debug_line_cu_end; - if (parse_debug_line_header(&p, &header)) + if (parse_debug_line_header(reader->obj, &p, &header)) return -1; reader->debug_line_cu_end = (char *)header.cu_end; + reader->debug_line_format = header.format; + reader->debug_line_version = header.version; reader->debug_line_directories = (char *)header.include_directories; reader->debug_line_files = (char *)header.filenames; @@ -1028,6 +1077,13 @@ di_read_debug_line_cu(DebugInfoReader *reader) } static void +set_addr_idx_value(DebugInfoValue *v, uint64_t n) +{ + v->as.addr_idx = n; + v->type = VAL_addr; +} + +static void set_uint_value(DebugInfoValue *v, uint64_t n) { v->as.uint64 = n; @@ -1074,19 +1130,39 @@ get_cstr_value(DebugInfoValue *v) } } +static const char * +resolve_strx(DebugInfoReader *reader, uint64_t idx) +{ + const char *p = reader->obj->debug_str_offsets.ptr + reader->current_str_offsets_base; + uint64_t off; + if (reader->format == 4) { + off = ((uint32_t *)p)[idx]; + } + else { + off = ((uint64_t *)p)[idx]; + } + return reader->obj->debug_str.ptr + off; +} + +static void +debug_info_reader_read_addr_value(DebugInfoReader *reader, DebugInfoValue *v) +{ + if (reader->address_size == 4) { + set_uint_value(v, read_uint32(&reader->p)); + } else if (reader->address_size == 8) { + set_uint_value(v, read_uint64(&reader->p)); + } else { + fprintf(stderr,"unknown address_size:%d", reader->address_size); + abort(); + } +} + static void debug_info_reader_read_value(DebugInfoReader *reader, uint64_t form, DebugInfoValue *v) { switch (form) { case DW_FORM_addr: - if (reader->address_size == 4) { - set_uint_value(v, read_uint32(&reader->p)); - } else if (reader->address_size == 8) { - set_uint_value(v, read_uint64(&reader->p)); - } else { - fprintf(stderr,"unknown address_size:%d", reader->address_size); - abort(); - } + debug_info_reader_read_addr_value(reader, v); break; case DW_FORM_block2: v->size = read_uint16(&reader->p); @@ -1138,13 +1214,19 @@ debug_info_reader_read_value(DebugInfoReader *reader, uint64_t form, DebugInfoVa set_uint_value(v, read_uleb128(reader)); break; case DW_FORM_ref_addr: - if (reader->format == 4) { - set_uint_value(v, read_uint32(&reader->p)); - } else if (reader->format == 8) { - set_uint_value(v, read_uint64(&reader->p)); + if (reader->current_version <= 2) { + // DWARF Version 2 specifies that references have + // the same size as an address on the target system + debug_info_reader_read_addr_value(reader, v); } else { - fprintf(stderr,"unknown format:%d", reader->format); - abort(); + if (reader->format == 4) { + set_uint_value(v, read_uint32(&reader->p)); + } else if (reader->format == 8) { + set_uint_value(v, read_uint64(&reader->p)); + } else { + fprintf(stderr,"unknown format:%d", reader->format); + abort(); + } } break; case DW_FORM_ref1: @@ -1186,11 +1268,10 @@ debug_info_reader_read_value(DebugInfoReader *reader, uint64_t form, DebugInfoVa set_uint_value(v, 1); break; case DW_FORM_strx: - set_uint_value(v, uleb128(&reader->p)); + set_cstr_value(v, resolve_strx(reader, uleb128(&reader->p))); break; case DW_FORM_addrx: - /* TODO: read .debug_addr */ - set_uint_value(v, uleb128(&reader->p)); + set_addr_idx_value(v, uleb128(&reader->p)); break; case DW_FORM_ref_sup4: set_uint_value(v, read_uint32(&reader->p)); @@ -1205,8 +1286,7 @@ debug_info_reader_read_value(DebugInfoReader *reader, uint64_t form, DebugInfoVa reader->p += v->size; break; case DW_FORM_line_strp: - set_uint_value(v, read_uint(reader)); - /* *p = reader->file + reader->line->sh_offset + ret; */ + set_cstrp_value(v, reader->obj->debug_line_str.ptr, read_uint(reader)); break; case DW_FORM_ref_sig8: set_uint_value(v, read_uint64(&reader->p)); @@ -1224,28 +1304,28 @@ debug_info_reader_read_value(DebugInfoReader *reader, uint64_t form, DebugInfoVa set_uint_value(v, read_uint64(&reader->p)); break; case DW_FORM_strx1: - set_uint_value(v, read_uint8(&reader->p)); + set_cstr_value(v, resolve_strx(reader, read_uint8(&reader->p))); break; case DW_FORM_strx2: - set_uint_value(v, read_uint16(&reader->p)); + set_cstr_value(v, resolve_strx(reader, read_uint16(&reader->p))); break; case DW_FORM_strx3: - set_uint_value(v, read_uint24(&reader->p)); + set_cstr_value(v, resolve_strx(reader, read_uint24(&reader->p))); break; case DW_FORM_strx4: - set_uint_value(v, read_uint32(&reader->p)); + set_cstr_value(v, resolve_strx(reader, read_uint32(&reader->p))); break; case DW_FORM_addrx1: - set_uint_value(v, read_uint8(&reader->p)); + set_addr_idx_value(v, read_uint8(&reader->p)); break; case DW_FORM_addrx2: - set_uint_value(v, read_uint16(&reader->p)); + set_addr_idx_value(v, read_uint16(&reader->p)); break; case DW_FORM_addrx3: - set_uint_value(v, read_uint24(&reader->p)); + set_addr_idx_value(v, read_uint24(&reader->p)); break; case DW_FORM_addrx4: - set_uint_value(v, read_uint32(&reader->p)); + set_addr_idx_value(v, read_uint32(&reader->p)); break; case 0: goto fail; @@ -1292,7 +1372,7 @@ hexdump0(const unsigned char *p, size_t n) for (i=0; i < n; i++){ switch (i & 15) { case 0: - fprintf(stderr, "%02zd: %02X ", i/16, p[i]); + fprintf(stderr, "%02" PRIdSIZE ": %02X ", i/16, p[i]); break; case 15: fprintf(stderr, "%02X\n", p[i]); @@ -1313,16 +1393,16 @@ div_inspect(DebugInfoValue *v) { switch (v->type) { case VAL_uint: - fprintf(stderr,"%d: type:%d size:%zx v:%"PRIx64"\n",__LINE__,v->type,v->size,v->as.uint64); + fprintf(stderr,"%d: type:%d size:%" PRIxSIZE " v:%"PRIx64"\n",__LINE__,v->type,v->size,v->as.uint64); break; case VAL_int: - fprintf(stderr,"%d: type:%d size:%zx v:%"PRId64"\n",__LINE__,v->type,v->size,(int64_t)v->as.uint64); + fprintf(stderr,"%d: type:%d size:%" PRIxSIZE " v:%"PRId64"\n",__LINE__,v->type,v->size,(int64_t)v->as.uint64); break; case VAL_cstr: - fprintf(stderr,"%d: type:%d size:%zx v:'%s'\n",__LINE__,v->type,v->size,v->as.ptr); + fprintf(stderr,"%d: type:%d size:%" PRIxSIZE " v:'%s'\n",__LINE__,v->type,v->size,v->as.ptr); break; case VAL_data: - fprintf(stderr,"%d: type:%d size:%zx v:\n",__LINE__,v->type,v->size); + fprintf(stderr,"%d: type:%d size:%" PRIxSIZE " v:\n",__LINE__,v->type,v->size); hexdump(v->as.ptr, 16); break; } @@ -1373,6 +1453,76 @@ di_skip_records(DebugInfoReader *reader) } } +typedef struct addr_header { + const char *ptr; + uint64_t unit_length; + uint8_t format; + uint8_t address_size; + /* uint8_t segment_selector_size; */ +} addr_header_t; + +static void +addr_header_init(obj_info_t *obj, addr_header_t *header) { + const char *p = obj->debug_addr.ptr; + + header->ptr = p; + + if (!p) return; + + header->unit_length = *(uint32_t *)p; + p += sizeof(uint32_t); + + header->format = 4; + if (header->unit_length == 0xffffffff) { + header->unit_length = *(uint64_t *)p; + p += sizeof(uint64_t); + header->format = 8; + } + + p += 2; /* version */ + header->address_size = *p++; + p++; /* segment_selector_size */ +} + +static uint64_t +read_addr(addr_header_t *header, uint64_t addr_base, uint64_t idx) { + if (header->address_size == 4) { + return ((uint32_t*)(header->ptr + addr_base))[idx]; + } + else { + return ((uint64_t*)(header->ptr + addr_base))[idx]; + } +} + +typedef struct rnglists_header { + uint64_t unit_length; + uint8_t format; + uint8_t address_size; + uint32_t offset_entry_count; +} rnglists_header_t; + +static void +rnglists_header_init(obj_info_t *obj, rnglists_header_t *header) { + const char *p = obj->debug_rnglists.ptr; + + if (!p) return; + + header->unit_length = *(uint32_t *)p; + p += sizeof(uint32_t); + + header->format = 4; + if (header->unit_length == 0xffffffff) { + header->unit_length = *(uint64_t *)p; + p += sizeof(uint64_t); + header->format = 8; + } + + p += 2; /* version */ + header->address_size = *p++; + p++; /* segment_selector_size */ + header->offset_entry_count = *(uint32_t *)p; +} + typedef struct { uint64_t low_pc; uint64_t high_pc; @@ -1383,24 +1533,31 @@ typedef struct { } ranges_t; static void -ranges_set(ranges_t *ptr, DebugInfoValue *v) +ranges_set(ranges_t *ptr, DebugInfoValue *v, addr_header_t *addr_header, uint64_t addr_base) { + uint64_t n = 0; + if (v->type == VAL_uint) { + n = v->as.uint64; + } + else if (v->type == VAL_addr) { + n = read_addr(addr_header, addr_base, v->as.addr_idx); + } switch (v->at) { case DW_AT_low_pc: - ptr->low_pc = v->as.uint64; + ptr->low_pc = n; ptr->low_pc_set = true; break; case DW_AT_high_pc: if (v->form == DW_FORM_addr) { - ptr->high_pc = v->as.uint64; + ptr->high_pc = n; } else { - ptr->high_pc = ptr->low_pc + v->as.uint64; + ptr->high_pc = ptr->low_pc + n; } ptr->high_pc_set = true; break; case DW_AT_ranges: - ptr->ranges = v->as.uint64; + ptr->ranges = n; ptr->ranges_set = true; break; } @@ -1422,7 +1579,7 @@ read_dw_form_addr(DebugInfoReader *reader, const char **ptr) } static uintptr_t -ranges_include(DebugInfoReader *reader, ranges_t *ptr, uint64_t addr) +ranges_include(DebugInfoReader *reader, ranges_t *ptr, uint64_t addr, rnglists_header_t *rnglists_header) { if (ptr->high_pc_set) { if (ptr->ranges_set || !ptr->low_pc_set) { @@ -1437,8 +1594,21 @@ ranges_include(DebugInfoReader *reader, ranges_t *ptr, uint64_t addr) const char *p; uint64_t base = ptr->low_pc_set ? ptr->low_pc : reader->current_low_pc; bool base_valid = true; - if (reader->obj->debug_rnglists.ptr) { - p = reader->obj->debug_rnglists.ptr + ptr->ranges; + if (reader->current_version >= 5) { + if (rnglists_header->offset_entry_count == 0) { + // DW_FORM_sec_offset + p = reader->obj->debug_rnglists.ptr + ptr->ranges + reader->current_rnglists_base; + } + else { + // DW_FORM_rnglistx + const char *offset_array = reader->obj->debug_rnglists.ptr + reader->current_rnglists_base; + if (rnglists_header->format == 4) { + p = offset_array + ((uint32_t *)offset_array)[ptr->ranges]; + } + else { + p = offset_array + ((uint64_t *)offset_array)[ptr->ranges]; + } + } for (;;) { uint8_t rle = read_uint8(&p); uintptr_t from = 0, to = 0; @@ -1548,6 +1718,7 @@ di_read_cu(DebugInfoReader *reader) } reader->cu_end = reader->p + unit_length; version = read_uint16(&reader->p); + reader->current_version = version; if (version > 5) { return -1; } @@ -1580,16 +1751,45 @@ di_read_cu(DebugInfoReader *reader) break; } + reader->current_str_offsets_base = 0; + reader->current_addr_base = 0; + reader->current_rnglists_base = 0; + + DebugInfoValue low_pc = {{}}; /* enumerate abbrev */ for (;;) { DebugInfoValue v = {{}}; if (!di_read_record(reader, &v)) break; switch (v.at) { case DW_AT_low_pc: - reader->current_low_pc = v.as.uint64; + // clang may output DW_AT_addr_base after DW_AT_low_pc. + // We need to resolve the DW_FORM_addr* after DW_AT_addr_base is parsed. + low_pc = v; + break; + case DW_AT_str_offsets_base: + reader->current_str_offsets_base = v.as.uint64; + break; + case DW_AT_addr_base: + reader->current_addr_base = v.as.uint64; + break; + case DW_AT_rnglists_base: + reader->current_rnglists_base = v.as.uint64; break; } } + // Resolve the DW_FORM_addr of DW_AT_low_pc + switch (low_pc.type) { + case VAL_uint: + reader->current_low_pc = low_pc.as.uint64; + break; + case VAL_addr: + { + addr_header_t header; + addr_header_init(reader->obj, &header); + reader->current_low_pc = read_addr(&header, reader->current_addr_base, low_pc.as.addr_idx); + } + break; + } } while (0); #endif return 0; @@ -1643,6 +1843,13 @@ read_abstract_origin(DebugInfoReader *reader, uint64_t form, uint64_t abstract_o static void debug_info_read(DebugInfoReader *reader, int num_traces, void **traces, line_info_t *lines, int offset) { + + addr_header_t addr_header = {}; + addr_header_init(reader->obj, &addr_header); + + rnglists_header_t rnglists_header = {}; + rnglists_header_init(reader->obj, &rnglists_header); + while (reader->p < reader->cu_end) { DIE die; ranges_t ranges = {}; @@ -1669,7 +1876,7 @@ debug_info_read(DebugInfoReader *reader, int num_traces, void **traces, line.sname = get_cstr_value(&v); break; case DW_AT_call_file: - fill_filename((int)v.as.uint64, reader->debug_line_directories, reader->debug_line_files, &line, reader->obj); + fill_filename((int)v.as.uint64, reader->debug_line_format, reader->debug_line_version, reader->debug_line_directories, reader->debug_line_files, &line, reader->obj); break; case DW_AT_call_line: line.line = (int)v.as.uint64; @@ -1677,7 +1884,7 @@ debug_info_read(DebugInfoReader *reader, int num_traces, void **traces, case DW_AT_low_pc: case DW_AT_high_pc: case DW_AT_ranges: - ranges_set(&ranges, &v); + ranges_set(&ranges, &v, &addr_header, reader->current_addr_base); break; case DW_AT_declaration: goto skip_die; @@ -1694,9 +1901,9 @@ debug_info_read(DebugInfoReader *reader, int num_traces, void **traces, for (int i=offset; i < num_traces; i++) { uintptr_t addr = (uintptr_t)traces[i]; uintptr_t offset = addr - reader->obj->base_addr + reader->obj->vmaddr; - uintptr_t saddr = ranges_include(reader, &ranges, offset); + uintptr_t saddr = ranges_include(reader, &ranges, offset, &rnglists_header); if (saddr) { - /* fprintf(stderr, "%d:%tx: %d %lx->%lx %x %s: %s/%s %d %s %s %s\n",__LINE__,die.pos, i,addr,offset, die.tag,line.sname,line.dirname,line.filename,line.line,reader->obj->path,line.sname,lines[i].sname); */ + /* fprintf(stdout, "%d:%tx: %d %lx->%lx %x %s: %s/%s %d %s %s %s\n",__LINE__,die.pos, i,addr,offset, die.tag,line.sname,line.dirname,line.filename,line.line,reader->obj->path,line.sname,lines[i].sname); */ if (lines[i].sname) { line_info_t *lp = malloc(sizeof(line_info_t)); memcpy(lp, &lines[i], sizeof(line_info_t)); @@ -1715,6 +1922,54 @@ debug_info_read(DebugInfoReader *reader, int num_traces, void **traces, } } +// This function parses the following attributes of Line Number Program Header in DWARF 5: +// +// * directory_entry_format_count +// * directory_entry_format +// * directories_count +// * directories +// +// or +// +// * file_name_entry_format_count +// * file_name_entry_format +// * file_names_count +// * file_names +// +// It records DW_LNCT_path and DW_LNCT_directory_index at the index "idx". +static const char * +parse_ver5_debug_line_header(const char *p, int idx, uint8_t format, obj_info_t *obj, const char **out_path, uint64_t *out_directory_index) { + int i, j; + int entry_format_count = *(uint8_t *)p++; + const char *entry_format = p; + + /* skip the part of entry_format */ + for (i = 0; i < entry_format_count * 2; i++) uleb128(&p); + + int entry_count = (int)uleb128(&p); + + DebugInfoReader reader; + debug_info_reader_init(&reader, obj); + reader.format = format; + reader.p = p; + for (j = 0; j < entry_count; j++) { + const char *format = entry_format; + for (i = 0; i < entry_format_count; i++) { + DebugInfoValue v = {{}}; + unsigned long dw_lnct = uleb128(&format); + unsigned long dw_form = uleb128(&format); + debug_info_reader_read_value(&reader, dw_form, &v); + if (dw_lnct == 1 /* DW_LNCT_path */ && v.type == VAL_cstr && out_path) + *out_path = v.as.ptr + v.off; + if (dw_lnct == 2 /* DW_LNCT_directory_index */ && v.type == VAL_uint && out_directory_index) + *out_directory_index = v.as.uint64; + } + if (j == idx) return 0; + } + + return reader.p; +} + #ifdef USE_ELF static unsigned long uncompress_debug_section(ElfW(Shdr) *shdr, char *file, char **ptr) @@ -1843,8 +2098,11 @@ fill_lines(int num_traces, void **traces, int check_debuglink, ".debug_info", ".debug_line", ".debug_ranges", + ".debug_str_offsets", + ".debug_addr", ".debug_rnglists", - ".debug_str" + ".debug_str", + ".debug_line_str" }; for (j=0; j < DWARF_SECTION_COUNT; j++) { @@ -2100,8 +2358,11 @@ found_mach_header: "__debug_info", "__debug_line", "__debug_ranges", + "__debug_str_offsets", + "__debug_addr", "__debug_rnglists", - "__debug_str" + "__debug_str", + "__debug_line_str", }; struct LP(segment_command) *scmd = (struct LP(segment_command) *)lcmd; if (strcmp(scmd->segname, "__TEXT") == 0) { @@ -2257,9 +2518,12 @@ print_line0(line_info_t *line, void *address) else if (!line->path) { kprintf("[0x%"PRIxPTR"]\n", addr); } - else if (!line->saddr || !line->sname) { + else if (!line->sname) { kprintf("%s(0x%"PRIxPTR") [0x%"PRIxPTR"]\n", line->path, addr-line->base_addr, addr); } + else if (!line->saddr) { + kprintf("%s(%s) [0x%"PRIxPTR"]\n", line->path, line->sname, addr); + } else if (line->line <= 0) { kprintf("%s(%s+0x%"PRIxPTR") [0x%"PRIxPTR"]\n", line->path, line->sname, d, addr); @@ -2296,6 +2560,7 @@ rb_dump_backtrace_with_lines(int num_traces, void **traces) obj_info_t *obj = NULL; /* 2 is NULL + main executable */ void **dladdr_fbases = (void **)calloc(num_traces+2, sizeof(void *)); + #ifdef HAVE_MAIN_EXE_PATH char *main_path = NULL; /* used on printing backtrace */ ssize_t len; @@ -2326,8 +2591,8 @@ rb_dump_backtrace_with_lines(int num_traces, void **traces) /* if the binary is strip-ed, this may effect */ for (p=dladdr_fbases; *p; p++) { if (*p == info.dli_fbase) { - lines[i].path = info.dli_fname; - lines[i].sname = info.dli_sname; + if (info.dli_fname) lines[i].path = info.dli_fname; + if (info.dli_sname) lines[i].sname = info.dli_sname; goto next_line; } } @@ -2337,9 +2602,11 @@ rb_dump_backtrace_with_lines(int num_traces, void **traces) obj->base_addr = (uintptr_t)info.dli_fbase; path = info.dli_fname; obj->path = path; - lines[i].path = path; - lines[i].sname = info.dli_sname; - lines[i].saddr = (uintptr_t)info.dli_saddr; + if (path) lines[i].path = path; + if (info.dli_sname) { + lines[i].sname = info.dli_sname; + lines[i].saddr = (uintptr_t)info.dli_saddr; + } strlcpy(binary_filename, path, PATH_MAX); if (fill_lines(num_traces, traces, 1, &obj, lines, i) == (uintptr_t)-1) break; @@ -39,6 +39,37 @@ VALUE rb_cArray; +/* Flags of RArray + * + * 1: RARRAY_EMBED_FLAG + * The array is embedded (its contents follow the header, rather than + * being on a separately allocated buffer). + * 2: RARRAY_SHARED_FLAG (equal to ELTS_SHARED) + * The array is shared. The buffer this array points to is owned by + * another array (the shared root). + * if USE_RVARGC + * 3-9: RARRAY_EMBED_LEN + * The length of the array when RARRAY_EMBED_FLAG is set. + * else + * 3-4: RARRAY_EMBED_LEN + * The length of the array when RARRAY_EMBED_FLAG is set. + * endif + * 12: RARRAY_SHARED_ROOT_FLAG + * The array is a shared root that does reference counting. The buffer + * this array points to is owned by this array but may be pointed to + * by other arrays. + * Note: Frozen arrays may be a shared root without this flag being + * set. Frozen arrays do not have reference counting because + * they cannot be modified. Not updating the reference count + * improves copy-on-write performance. Their reference count is + * assumed to be infinity. + * 13: RARRAY_TRANSIENT_FLAG + * The buffer of the array is allocated on the transient heap. + * 14: RARRAY_PTR_IN_USE_FLAG + * The buffer of the array is in use. This is only used during + * debugging. + */ + /* for OPTIMIZED_CMP: */ #define id_cmp idCmp @@ -53,23 +84,6 @@ should_be_T_ARRAY(VALUE ary) return RB_TYPE_P(ary, T_ARRAY); } -RBIMPL_ATTR_MAYBE_UNUSED() -static int -should_not_be_shared_and_embedded(VALUE ary) -{ - return !FL_TEST((ary), ELTS_SHARED) || !FL_TEST((ary), RARRAY_EMBED_FLAG); -} - -#define ARY_SHARED_P(ary) \ - (assert(should_be_T_ARRAY((VALUE)(ary))), \ - assert(should_not_be_shared_and_embedded((VALUE)ary)), \ - FL_TEST_RAW((ary),ELTS_SHARED)!=0) - -#define ARY_EMBED_P(ary) \ - (assert(should_be_T_ARRAY((VALUE)(ary))), \ - assert(should_not_be_shared_and_embedded((VALUE)ary)), \ - FL_TEST_RAW((ary), RARRAY_EMBED_FLAG) != 0) - #define ARY_HEAP_PTR(a) (assert(!ARY_EMBED_P(a)), RARRAY(a)->as.heap.ptr) #define ARY_HEAP_LEN(a) (assert(!ARY_EMBED_P(a)), RARRAY(a)->as.heap.len) #define ARY_HEAP_CAPA(a) (assert(!ARY_EMBED_P(a)), assert(!ARY_SHARED_ROOT_P(a)), \ @@ -79,11 +93,11 @@ should_not_be_shared_and_embedded(VALUE ary) #define ARY_EMBED_LEN(a) \ (assert(ARY_EMBED_P(a)), \ (long)((RBASIC(a)->flags >> RARRAY_EMBED_LEN_SHIFT) & \ - (RARRAY_EMBED_LEN_MASK >> RARRAY_EMBED_LEN_SHIFT))) + (RARRAY_EMBED_LEN_MASK >> RARRAY_EMBED_LEN_SHIFT))) #define ARY_HEAP_SIZE(a) (assert(!ARY_EMBED_P(a)), assert(ARY_OWNS_HEAP_P(a)), ARY_CAPA(a) * sizeof(VALUE)) #define ARY_OWNS_HEAP_P(a) (assert(should_be_T_ARRAY((VALUE)(a))), \ - !FL_TEST_RAW((a), ELTS_SHARED|RARRAY_EMBED_FLAG)) + !FL_TEST_RAW((a), RARRAY_SHARED_FLAG|RARRAY_EMBED_FLAG)) #define FL_SET_EMBED(a) do { \ assert(!ARY_SHARED_P(a)); \ @@ -95,9 +109,9 @@ should_not_be_shared_and_embedded(VALUE ary) #define FL_UNSET_EMBED(ary) FL_UNSET((ary), RARRAY_EMBED_FLAG|RARRAY_EMBED_LEN_MASK) #define FL_SET_SHARED(ary) do { \ assert(!ARY_EMBED_P(ary)); \ - FL_SET((ary), ELTS_SHARED); \ + FL_SET((ary), RARRAY_SHARED_FLAG); \ } while (0) -#define FL_UNSET_SHARED(ary) FL_UNSET((ary), ELTS_SHARED) +#define FL_UNSET_SHARED(ary) FL_UNSET((ary), RARRAY_SHARED_FLAG) #define ARY_SET_PTR(ary, p) do { \ assert(!ARY_EMBED_P(ary)); \ @@ -107,7 +121,6 @@ should_not_be_shared_and_embedded(VALUE ary) #define ARY_SET_EMBED_LEN(ary, n) do { \ long tmp_n = (n); \ assert(ARY_EMBED_P(ary)); \ - assert(!OBJ_FROZEN(ary)); \ RBASIC(ary)->flags &= ~RARRAY_EMBED_LEN_MASK; \ RBASIC(ary)->flags |= (tmp_n) << RARRAY_EMBED_LEN_SHIFT; \ } while (0) @@ -139,7 +152,7 @@ should_not_be_shared_and_embedded(VALUE ary) } \ } while (0) -#define ARY_CAPA(ary) (ARY_EMBED_P(ary) ? RARRAY_EMBED_LEN_MAX : \ +#define ARY_CAPA(ary) (ARY_EMBED_P(ary) ? ary_embed_capa(ary) : \ ARY_SHARED_ROOT_P(ary) ? RARRAY_LEN(ary) : ARY_HEAP_CAPA(ary)) #define ARY_SET_CAPA(ary, n) do { \ assert(!ARY_EMBED_P(ary)); \ @@ -148,26 +161,25 @@ should_not_be_shared_and_embedded(VALUE ary) RARRAY(ary)->as.heap.aux.capa = (n); \ } while (0) -#define ARY_SHARED_ROOT(ary) (assert(ARY_SHARED_P(ary)), RARRAY(ary)->as.heap.aux.shared_root) #define ARY_SET_SHARED(ary, value) do { \ const VALUE _ary_ = (ary); \ const VALUE _value_ = (value); \ assert(!ARY_EMBED_P(_ary_)); \ assert(ARY_SHARED_P(_ary_)); \ - assert(ARY_SHARED_ROOT_P(_value_)); \ + assert(!OBJ_FROZEN(_ary_)); \ + assert(ARY_SHARED_ROOT_P(_value_) || OBJ_FROZEN(_value_)); \ RB_OBJ_WRITE(_ary_, &RARRAY(_ary_)->as.heap.aux.shared_root, _value_); \ } while (0) -#define RARRAY_SHARED_ROOT_FLAG FL_USER5 -#define ARY_SHARED_ROOT_P(ary) (assert(should_be_T_ARRAY((VALUE)(ary))), \ - FL_TEST_RAW((ary), RARRAY_SHARED_ROOT_FLAG)) -#define ARY_SHARED_ROOT_REFCNT(ary) \ - (assert(ARY_SHARED_ROOT_P(ary)), RARRAY(ary)->as.heap.aux.capa) -#define ARY_SHARED_ROOT_OCCUPIED(ary) (ARY_SHARED_ROOT_REFCNT(ary) == 1) + +#define ARY_SHARED_ROOT_OCCUPIED(ary) (!OBJ_FROZEN(ary) && ARY_SHARED_ROOT_REFCNT(ary) == 1) #define ARY_SET_SHARED_ROOT_REFCNT(ary, value) do { \ assert(ARY_SHARED_ROOT_P(ary)); \ + assert(!OBJ_FROZEN(ary)); \ + assert((value) >= 0); \ RARRAY(ary)->as.heap.aux.capa = (value); \ } while (0) #define FL_SET_SHARED_ROOT(ary) do { \ + assert(!OBJ_FROZEN(ary)); \ assert(!ARY_EMBED_P(ary)); \ assert(!RARRAY_TRANSIENT_P(ary)); \ FL_SET((ary), RARRAY_SHARED_ROOT_FLAG); \ @@ -183,6 +195,65 @@ ARY_SET(VALUE a, long i, VALUE v) } #undef RARRAY_ASET +static long +ary_embed_capa(VALUE ary) +{ +#if USE_RVARGC + size_t size = rb_gc_obj_slot_size(ary) - offsetof(struct RArray, as.ary); + assert(size % sizeof(VALUE) == 0); + return size / sizeof(VALUE); +#else + return RARRAY_EMBED_LEN_MAX; +#endif +} + +static size_t +ary_embed_size(long capa) +{ + return offsetof(struct RArray, as.ary) + (sizeof(VALUE) * capa); +} + +static bool +ary_embeddable_p(long capa) +{ +#if USE_RVARGC + return rb_gc_size_allocatable_p(ary_embed_size(capa)); +#else + return capa <= RARRAY_EMBED_LEN_MAX; +#endif +} + +bool +rb_ary_embeddable_p(VALUE ary) +{ + /* An array cannot be turned embeddable when the array is: + * - Shared root: other objects may point to the buffer of this array + * so we cannot make it embedded. + * - Frozen: this array may also be a shared root without the shared root + * flag. + * - Shared: we don't want to re-embed an array that points to a shared + * root (to save memory). + */ + return !(ARY_SHARED_ROOT_P(ary) || OBJ_FROZEN(ary) || ARY_SHARED_P(ary)); +} + +size_t +rb_ary_size_as_embedded(VALUE ary) +{ + size_t real_size; + + if (ARY_EMBED_P(ary)) { + real_size = ary_embed_size(ARY_EMBED_LEN(ary)); + } + else if (rb_ary_embeddable_p(ary)) { + real_size = ary_embed_size(ARY_HEAP_CAPA(ary)); + } + else { + real_size = sizeof(struct RArray); + } + return real_size; +} + #if ARRAY_DEBUG #define ary_verify(ary) ary_verify_(ary, __FILE__, __LINE__) @@ -192,19 +263,19 @@ ary_verify_(VALUE ary, const char *file, int line) { assert(RB_TYPE_P(ary, T_ARRAY)); - if (FL_TEST(ary, ELTS_SHARED)) { - VALUE root = RARRAY(ary)->as.heap.aux.shared_root; + if (ARY_SHARED_P(ary)) { + VALUE root = ARY_SHARED_ROOT(ary); const VALUE *ptr = ARY_HEAP_PTR(ary); const VALUE *root_ptr = RARRAY_CONST_PTR_TRANSIENT(root); long len = ARY_HEAP_LEN(ary), root_len = RARRAY_LEN(root); - assert(FL_TEST(root, RARRAY_SHARED_ROOT_FLAG)); + assert(ARY_SHARED_ROOT_P(root) || OBJ_FROZEN(root)); assert(root_ptr <= ptr && ptr + len <= root_ptr + root_len); ary_verify(root); } else if (ARY_EMBED_P(ary)) { assert(!RARRAY_TRANSIENT_P(ary)); assert(!ARY_SHARED_P(ary)); - assert(RARRAY_LEN(ary) <= RARRAY_EMBED_LEN_MAX); + assert(RARRAY_LEN(ary) <= ary_embed_capa(ary)); } else { #if 1 @@ -260,7 +331,7 @@ void rb_mem_clear(VALUE *mem, long size) { while (size--) { - *mem++ = Qnil; + *mem++ = Qnil; } } @@ -268,7 +339,7 @@ static void ary_mem_clear(VALUE ary, long beg, long size) { RARRAY_PTR_USE_TRANSIENT(ary, ptr, { - rb_mem_clear(ptr + beg, size); + rb_mem_clear(ptr + beg, size); }); } @@ -276,7 +347,7 @@ static inline void memfill(register VALUE *mem, register long size, register VALUE val) { while (size--) { - *mem++ = val; + *mem++ = val; } } @@ -284,8 +355,8 @@ static void ary_memfill(VALUE ary, long beg, long size, VALUE val) { RARRAY_PTR_USE_TRANSIENT(ary, ptr, { - memfill(ptr + beg, size, val); - RB_OBJ_WRITTEN(ary, Qundef, val); + memfill(ptr + beg, size, val); + RB_OBJ_WRITTEN(ary, Qundef, val); }); } @@ -390,14 +461,11 @@ static inline void rb_ary_transient_heap_evacuate_(VALUE ary, int transient, int promote) { if (transient) { + assert(!ARY_SHARED_ROOT_P(ary)); + VALUE *new_ptr; const VALUE *old_ptr = ARY_HEAP_PTR(ary); long capa = ARY_HEAP_CAPA(ary); - long len = ARY_HEAP_LEN(ary); - - if (ARY_SHARED_ROOT_P(ary)) { - capa = len; - } assert(ARY_OWNS_HEAP_P(ary)); assert(RARRAY_TRANSIENT_P(ary)); @@ -439,6 +507,27 @@ rb_ary_detransient(VALUE ary) } #endif +void +rb_ary_make_embedded(VALUE ary) +{ + assert(rb_ary_embeddable_p(ary)); + if (!ARY_EMBED_P(ary)) { + const VALUE *buf = ARY_HEAP_PTR(ary); + long len = ARY_HEAP_LEN(ary); + bool was_transient = RARRAY_TRANSIENT_P(ary); + + // FL_SET_EMBED also unsets the transient flag + FL_SET_EMBED(ary); + ARY_SET_EMBED_LEN(ary, len); + + MEMCPY((void *)ARY_EMBED_PTR(ary), (void *)buf, VALUE, len); + + if (!was_transient) { + ary_heap_free_ptr(ary, buf, len * sizeof(VALUE)); + } + } +} + static void ary_resize_capa(VALUE ary, long capacity) { @@ -446,7 +535,7 @@ ary_resize_capa(VALUE ary, long capacity) assert(!OBJ_FROZEN(ary)); assert(!ARY_SHARED_P(ary)); - if (capacity > RARRAY_EMBED_LEN_MAX) { + if (capacity > ary_embed_capa(ary)) { size_t new_capa = capacity; if (ARY_EMBED_P(ary)) { long len = ARY_EMBED_LEN(ary); @@ -498,10 +587,10 @@ ary_double_capa(VALUE ary, long min) long new_capa = ARY_CAPA(ary) / 2; if (new_capa < ARY_DEFAULT_SIZE) { - new_capa = ARY_DEFAULT_SIZE; + new_capa = ARY_DEFAULT_SIZE; } if (new_capa >= ARY_MAX_SIZE - min) { - new_capa = (ARY_MAX_SIZE - min) / 2; + new_capa = (ARY_MAX_SIZE - min) / 2; } new_capa += min; ary_resize_capa(ary, new_capa); @@ -512,35 +601,40 @@ ary_double_capa(VALUE ary, long min) static void rb_ary_decrement_share(VALUE shared_root) { - if (shared_root) { - long num = ARY_SHARED_ROOT_REFCNT(shared_root) - 1; - if (num > 0) { - ARY_SET_SHARED_ROOT_REFCNT(shared_root, num); - } + if (!OBJ_FROZEN(shared_root)) { + long num = ARY_SHARED_ROOT_REFCNT(shared_root); + ARY_SET_SHARED_ROOT_REFCNT(shared_root, num - 1); } } static void rb_ary_unshare(VALUE ary) { - VALUE shared_root = RARRAY(ary)->as.heap.aux.shared_root; + VALUE shared_root = ARY_SHARED_ROOT(ary); rb_ary_decrement_share(shared_root); FL_UNSET_SHARED(ary); } -static inline void -rb_ary_unshare_safe(VALUE ary) +static void +rb_ary_reset(VALUE ary) { - if (ARY_SHARED_P(ary) && !ARY_EMBED_P(ary)) { - rb_ary_unshare(ary); + if (ARY_OWNS_HEAP_P(ary)) { + ary_heap_free(ary); + } + else if (ARY_SHARED_P(ary)) { + rb_ary_unshare(ary); } + + FL_SET_EMBED(ary); + ARY_SET_EMBED_LEN(ary, 0); } static VALUE rb_ary_increment_share(VALUE shared_root) { - long num = ARY_SHARED_ROOT_REFCNT(shared_root); - if (num >= 0) { + if (!OBJ_FROZEN(shared_root)) { + long num = ARY_SHARED_ROOT_REFCNT(shared_root); + assert(num >= 0); ARY_SET_SHARED_ROOT_REFCNT(shared_root, num + 1); } return shared_root; @@ -571,7 +665,7 @@ rb_ary_cancel_sharing(VALUE ary) ary_verify(shared_root); - if (len <= RARRAY_EMBED_LEN_MAX) { + if (len <= ary_embed_capa(ary)) { const VALUE *ptr = ARY_HEAP_PTR(ary); FL_UNSET_SHARED(ary); FL_SET_EMBED(ary); @@ -618,40 +712,40 @@ ary_ensure_room_for_push(VALUE ary, long add_len) long capa; if (old_len > ARY_MAX_SIZE - add_len) { - rb_raise(rb_eIndexError, "index %ld too big", new_len); + rb_raise(rb_eIndexError, "index %ld too big", new_len); } if (ARY_SHARED_P(ary)) { - if (new_len > RARRAY_EMBED_LEN_MAX) { + if (new_len > ary_embed_capa(ary)) { VALUE shared_root = ARY_SHARED_ROOT(ary); if (ARY_SHARED_ROOT_OCCUPIED(shared_root)) { if (ARY_HEAP_PTR(ary) - RARRAY_CONST_PTR_TRANSIENT(shared_root) + new_len <= RARRAY_LEN(shared_root)) { - rb_ary_modify_check(ary); + rb_ary_modify_check(ary); ary_verify(ary); ary_verify(shared_root); return shared_root; - } - else { - /* if array is shared, then it is likely it participate in push/shift pattern */ - rb_ary_modify(ary); - capa = ARY_CAPA(ary); - if (new_len > capa - (capa >> 6)) { - ary_double_capa(ary, new_len); - } + } + else { + /* if array is shared, then it is likely it participate in push/shift pattern */ + rb_ary_modify(ary); + capa = ARY_CAPA(ary); + if (new_len > capa - (capa >> 6)) { + ary_double_capa(ary, new_len); + } ary_verify(ary); - return ary; - } - } - } + return ary; + } + } + } ary_verify(ary); rb_ary_modify(ary); } else { - rb_ary_modify_check(ary); + rb_ary_modify_check(ary); } capa = ARY_CAPA(ary); if (new_len > capa) { - ary_double_capa(ary, new_len); + ary_double_capa(ary, new_len); } ary_verify(ary); @@ -663,6 +757,7 @@ ary_ensure_room_for_push(VALUE ary, long add_len) * array.freeze -> self * * Freezes +self+; returns +self+: + * * a = [] * a.frozen? # => false * a.freeze @@ -688,18 +783,25 @@ VALUE rb_ary_shared_with_p(VALUE ary1, VALUE ary2) { if (!ARY_EMBED_P(ary1) && ARY_SHARED_P(ary1) && - !ARY_EMBED_P(ary2) && ARY_SHARED_P(ary2) && - RARRAY(ary1)->as.heap.aux.shared_root == RARRAY(ary2)->as.heap.aux.shared_root && - RARRAY(ary1)->as.heap.len == RARRAY(ary2)->as.heap.len) { - return Qtrue; + !ARY_EMBED_P(ary2) && ARY_SHARED_P(ary2) && + ARY_SHARED_ROOT(ary1) == ARY_SHARED_ROOT(ary2) && + ARY_HEAP_LEN(ary1) == ARY_HEAP_LEN(ary2)) { + return Qtrue; } return Qfalse; } static VALUE -ary_alloc(VALUE klass) +ary_alloc_embed(VALUE klass, long capa) { - NEWOBJ_OF(ary, struct RArray, klass, T_ARRAY | RARRAY_EMBED_FLAG | (RGENGC_WB_PROTECTED_ARRAY ? FL_WB_PROTECTED : 0)); + size_t size = ary_embed_size(capa); + assert(rb_gc_size_allocatable_p(size)); +#if !USE_RVARGC + assert(size <= sizeof(struct RArray)); +#endif + RVARGC_NEWOBJ_OF(ary, struct RArray, klass, + T_ARRAY | RARRAY_EMBED_FLAG | (RGENGC_WB_PROTECTED_ARRAY ? FL_WB_PROTECTED : 0), + size); /* Created array is: * FL_SET_EMBED((VALUE)ary); * ARY_SET_EMBED_LEN((VALUE)ary, 0); @@ -708,10 +810,19 @@ ary_alloc(VALUE klass) } static VALUE +ary_alloc_heap(VALUE klass) +{ + RVARGC_NEWOBJ_OF(ary, struct RArray, klass, + T_ARRAY | (RGENGC_WB_PROTECTED_ARRAY ? FL_WB_PROTECTED : 0), + sizeof(struct RArray)); + return (VALUE)ary; +} + +static VALUE empty_ary_alloc(VALUE klass) { RUBY_DTRACE_CREATE_HOOK(ARRAY, 0); - return ary_alloc(klass); + return ary_alloc_embed(klass, 0); } static VALUE @@ -720,20 +831,24 @@ ary_new(VALUE klass, long capa) VALUE ary,*ptr; if (capa < 0) { - rb_raise(rb_eArgError, "negative array size (or size too big)"); + rb_raise(rb_eArgError, "negative array size (or size too big)"); } if (capa > ARY_MAX_SIZE) { - rb_raise(rb_eArgError, "array size too big"); + rb_raise(rb_eArgError, "array size too big"); } RUBY_DTRACE_CREATE_HOOK(ARRAY, capa); - ary = ary_alloc(klass); - if (capa > RARRAY_EMBED_LEN_MAX) { + if (ary_embeddable_p(capa)) { + ary = ary_alloc_embed(klass, capa); + } + else { + ary = ary_alloc_heap(klass); + ARY_SET_CAPA(ary, capa); + assert(!ARY_EMBED_P(ary)); + ptr = ary_heap_alloc(ary, capa); - FL_UNSET_EMBED(ary); ARY_SET_PTR(ary, ptr); - ARY_SET_CAPA(ary, capa); ARY_SET_HEAP_LEN(ary, 0); } @@ -749,7 +864,7 @@ rb_ary_new_capa(long capa) VALUE rb_ary_new(void) { - return rb_ary_new2(RARRAY_EMBED_LEN_MAX); + return rb_ary_new_capa(0); } VALUE @@ -763,7 +878,7 @@ VALUE va_start(ar, n); for (i=0; i<n; i++) { - ARY_SET(ary, i, va_arg(ar, VALUE)); + ARY_SET(ary, i, va_arg(ar, VALUE)); } va_end(ar); @@ -778,8 +893,8 @@ rb_ary_tmp_new_from_values(VALUE klass, long n, const VALUE *elts) ary = ary_new(klass, n); if (n > 0 && elts) { - ary_memcpy(ary, 0, n, elts); - ARY_SET_LEN(ary, n); + ary_memcpy(ary, 0, n, elts); + ARY_SET_LEN(ary, n); } return ary; @@ -792,9 +907,16 @@ rb_ary_new_from_values(long n, const VALUE *elts) } static VALUE -ec_ary_alloc(rb_execution_context_t *ec, VALUE klass) +ec_ary_alloc_embed(rb_execution_context_t *ec, VALUE klass, long capa) { - RB_EC_NEWOBJ_OF(ec, ary, struct RArray, klass, T_ARRAY | RARRAY_EMBED_FLAG | (RGENGC_WB_PROTECTED_ARRAY ? FL_WB_PROTECTED : 0)); + size_t size = ary_embed_size(capa); + assert(rb_gc_size_allocatable_p(size)); +#if !USE_RVARGC + assert(size <= sizeof(struct RArray)); +#endif + RB_RVARGC_EC_NEWOBJ_OF(ec, ary, struct RArray, klass, + T_ARRAY | RARRAY_EMBED_FLAG | (RGENGC_WB_PROTECTED_ARRAY ? FL_WB_PROTECTED : 0), + size); /* Created array is: * FL_SET_EMBED((VALUE)ary); * ARY_SET_EMBED_LEN((VALUE)ary, 0); @@ -803,26 +925,38 @@ ec_ary_alloc(rb_execution_context_t *ec, VALUE klass) } static VALUE +ec_ary_alloc_heap(rb_execution_context_t *ec, VALUE klass) +{ + RB_RVARGC_EC_NEWOBJ_OF(ec, ary, struct RArray, klass, + T_ARRAY | (RGENGC_WB_PROTECTED_ARRAY ? FL_WB_PROTECTED : 0), + sizeof(struct RArray)); + return (VALUE)ary; +} + +static VALUE ec_ary_new(rb_execution_context_t *ec, VALUE klass, long capa) { VALUE ary,*ptr; if (capa < 0) { - rb_raise(rb_eArgError, "negative array size (or size too big)"); + rb_raise(rb_eArgError, "negative array size (or size too big)"); } if (capa > ARY_MAX_SIZE) { - rb_raise(rb_eArgError, "array size too big"); + rb_raise(rb_eArgError, "array size too big"); } RUBY_DTRACE_CREATE_HOOK(ARRAY, capa); - ary = ec_ary_alloc(ec, klass); + if (ary_embeddable_p(capa)) { + ary = ec_ary_alloc_embed(ec, klass, capa); + } + else { + ary = ec_ary_alloc_heap(ec, klass); + ARY_SET_CAPA(ary, capa); + assert(!ARY_EMBED_P(ary)); - if (capa > RARRAY_EMBED_LEN_MAX) { ptr = ary_heap_alloc(ary, capa); - FL_UNSET_EMBED(ary); ARY_SET_PTR(ary, ptr); - ARY_SET_CAPA(ary, capa); ARY_SET_HEAP_LEN(ary, 0); } @@ -836,15 +970,15 @@ rb_ec_ary_new_from_values(rb_execution_context_t *ec, long n, const VALUE *elts) ary = ec_ary_new(ec, rb_cArray, n); if (n > 0 && elts) { - ary_memcpy(ary, 0, n, elts); - ARY_SET_LEN(ary, n); + ary_memcpy(ary, 0, n, elts); + ARY_SET_LEN(ary, n); } return ary; } VALUE -rb_ary_tmp_new(long capa) +rb_ary_hidden_new(long capa) { VALUE ary = ary_new(0, capa); rb_ary_transient_heap_evacuate(ary, TRUE); @@ -852,12 +986,11 @@ rb_ary_tmp_new(long capa) } VALUE -rb_ary_tmp_new_fill(long capa) +rb_ary_hidden_new_fill(long capa) { - VALUE ary = ary_new(0, capa); + VALUE ary = rb_ary_hidden_new(capa); ary_memfill(ary, 0, capa, Qnil); ARY_SET_LEN(ary, capa); - rb_ary_transient_heap_evacuate(ary, TRUE); return ary; } @@ -895,64 +1028,69 @@ RUBY_FUNC_EXPORTED size_t rb_ary_memsize(VALUE ary) { if (ARY_OWNS_HEAP_P(ary)) { - return ARY_CAPA(ary) * sizeof(VALUE); + return ARY_CAPA(ary) * sizeof(VALUE); } else { - return 0; + return 0; } } -static inline void -ary_discard(VALUE ary) -{ - rb_ary_free(ary); - RBASIC(ary)->flags |= RARRAY_EMBED_FLAG; - RBASIC(ary)->flags &= ~(RARRAY_EMBED_LEN_MASK | RARRAY_TRANSIENT_FLAG); -} - static VALUE ary_make_shared(VALUE ary) { - assert(!ARY_EMBED_P(ary)); + assert(USE_RVARGC || !ARY_EMBED_P(ary)); ary_verify(ary); if (ARY_SHARED_P(ary)) { return ARY_SHARED_ROOT(ary); } else if (ARY_SHARED_ROOT_P(ary)) { - return ary; + return ary; } else if (OBJ_FROZEN(ary)) { - rb_ary_transient_heap_evacuate(ary, TRUE); - ary_shrink_capa(ary); - FL_SET_SHARED_ROOT(ary); - ARY_SET_SHARED_ROOT_REFCNT(ary, 1); - return ary; + if (!ARY_EMBED_P(ary)) { + rb_ary_transient_heap_evacuate(ary, TRUE); + ary_shrink_capa(ary); + } + return ary; } else { - long capa = ARY_CAPA(ary), len = RARRAY_LEN(ary); - const VALUE *ptr; - NEWOBJ_OF(shared, struct RArray, 0, T_ARRAY | (RGENGC_WB_PROTECTED_ARRAY ? FL_WB_PROTECTED : 0)); - VALUE vshared = (VALUE)shared; - rb_ary_transient_heap_evacuate(ary, TRUE); - ptr = ARY_HEAP_PTR(ary); - - FL_UNSET_EMBED(vshared); - ARY_SET_LEN(vshared, capa); - ARY_SET_PTR(vshared, ptr); - ary_mem_clear(vshared, len, capa - len); - FL_SET_SHARED_ROOT(vshared); - ARY_SET_SHARED_ROOT_REFCNT(vshared, 1); - FL_SET_SHARED(ary); + + long capa = ARY_CAPA(ary); + long len = RARRAY_LEN(ary); + + /* Shared roots cannot be embedded because the reference count + * (refcnt) is stored in as.heap.aux.capa. */ + VALUE shared = ary_alloc_heap(0); + FL_SET_SHARED_ROOT(shared); + + if (ARY_EMBED_P(ary)) { + /* Cannot use ary_heap_alloc because we don't want to allocate + * on the transient heap. */ + VALUE *ptr = ALLOC_N(VALUE, capa); + ARY_SET_PTR(shared, ptr); + ary_memcpy(shared, 0, len, RARRAY_PTR(ary)); + + FL_UNSET_EMBED(ary); + ARY_SET_HEAP_LEN(ary, len); + ARY_SET_PTR(ary, ptr); + } + else { + ARY_SET_PTR(shared, RARRAY_PTR(ary)); + } + + ARY_SET_LEN(shared, capa); + ary_mem_clear(shared, len, capa - len); + ARY_SET_SHARED_ROOT_REFCNT(shared, 1); + FL_SET_SHARED(ary); RB_DEBUG_COUNTER_INC(obj_ary_shared_create); - ARY_SET_SHARED(ary, vshared); - OBJ_FREEZE(vshared); + ARY_SET_SHARED(ary, shared); - ary_verify(vshared); + ary_verify(shared); ary_verify(ary); - return vshared; + return shared; } } @@ -961,8 +1099,10 @@ ary_make_substitution(VALUE ary) { long len = RARRAY_LEN(ary); - if (len <= RARRAY_EMBED_LEN_MAX) { - VALUE subst = rb_ary_new2(len); + if (ary_embeddable_p(len)) { + VALUE subst = rb_ary_new_capa(len); + assert(ARY_EMBED_P(subst)); + ary_memcpy(subst, 0, len, RARRAY_CONST_PTR_TRANSIENT(ary)); ARY_SET_EMBED_LEN(subst, len); return subst; @@ -1023,6 +1163,30 @@ rb_ary_s_try_convert(VALUE dummy, VALUE ary) return rb_check_array_type(ary); } +/* :nodoc: */ +static VALUE +rb_ary_s_new(int argc, VALUE *argv, VALUE klass) +{ + VALUE ary; + + if (klass == rb_cArray) { + long size = 0; + if (argc > 0 && FIXNUM_P(argv[0])) { + size = FIX2LONG(argv[0]); + if (size < 0) size = 0; + } + + ary = ary_new(klass, size); + + rb_obj_call_init_kw(ary, argc, argv, RB_PASS_CALLED_KEYWORDS); + } + else { + ary = rb_class_new_instance_pass_kw(argc, argv, klass); + } + + return ary; +} + /* * call-seq: * Array.new -> new_empty_array @@ -1037,6 +1201,7 @@ rb_ary_s_try_convert(VALUE dummy, VALUE ary) * * With no block and a single \Array argument +array+, * returns a new \Array formed from +array+: + * * a = Array.new([:foo, 'bar', 2]) * a.class # => Array * a # => [:foo, "bar", 2] @@ -1044,12 +1209,14 @@ rb_ary_s_try_convert(VALUE dummy, VALUE ary) * With no block and a single \Integer argument +size+, * returns a new \Array of the given size * whose elements are all +nil+: + * * a = Array.new(3) * a # => [nil, nil, nil] * * With no block and arguments +size+ and +default_value+, * returns an \Array of the given size; * each element is that same +default_value+: + * * a = Array.new(3, 'x') * a # => ['x', 'x', 'x'] * @@ -1057,6 +1224,7 @@ rb_ary_s_try_convert(VALUE dummy, VALUE ary) * returns an \Array of the given size; * the block is called with each successive integer +index+; * the element for that +index+ is the return value from the block: + * * a = Array.new(3) {|index| "Element #{index}" } * a # => ["Element 0", "Element 1", "Element 2"] * @@ -1075,51 +1243,48 @@ rb_ary_initialize(int argc, VALUE *argv, VALUE ary) rb_ary_modify(ary); if (argc == 0) { - if (ARY_OWNS_HEAP_P(ary) && ARY_HEAP_PTR(ary) != NULL) { - ary_heap_free(ary); - } - rb_ary_unshare_safe(ary); - FL_SET_EMBED(ary); - ARY_SET_EMBED_LEN(ary, 0); - if (rb_block_given_p()) { - rb_warning("given block not used"); - } - return ary; + rb_ary_reset(ary); + assert(ARY_EMBED_P(ary)); + assert(ARY_EMBED_LEN(ary) == 0); + if (rb_block_given_p()) { + rb_warning("given block not used"); + } + return ary; } rb_scan_args(argc, argv, "02", &size, &val); if (argc == 1 && !FIXNUM_P(size)) { - val = rb_check_array_type(size); - if (!NIL_P(val)) { - rb_ary_replace(ary, val); - return ary; - } + val = rb_check_array_type(size); + if (!NIL_P(val)) { + rb_ary_replace(ary, val); + return ary; + } } len = NUM2LONG(size); /* NUM2LONG() may call size.to_int, ary can be frozen, modified, etc */ if (len < 0) { - rb_raise(rb_eArgError, "negative array size"); + rb_raise(rb_eArgError, "negative array size"); } if (len > ARY_MAX_SIZE) { - rb_raise(rb_eArgError, "array size too big"); + rb_raise(rb_eArgError, "array size too big"); } /* recheck after argument conversion */ rb_ary_modify(ary); ary_resize_capa(ary, len); if (rb_block_given_p()) { - long i; + long i; - if (argc == 2) { - rb_warn("block supersedes default value argument"); - } - for (i=0; i<len; i++) { - rb_ary_store(ary, i, rb_yield(LONG2NUM(i))); - ARY_SET_LEN(ary, i + 1); - } + if (argc == 2) { + rb_warn("block supersedes default value argument"); + } + for (i=0; i<len; i++) { + rb_ary_store(ary, i, rb_yield(LONG2NUM(i))); + ARY_SET_LEN(ary, i + 1); + } } else { - ary_memfill(ary, 0, len, val); - ARY_SET_LEN(ary, len); + ary_memfill(ary, 0, len, val); + ARY_SET_LEN(ary, len); } return ary; } @@ -1150,26 +1315,26 @@ rb_ary_store(VALUE ary, long idx, VALUE val) long len = RARRAY_LEN(ary); if (idx < 0) { - idx += len; - if (idx < 0) { - rb_raise(rb_eIndexError, "index %ld too small for array; minimum: %ld", - idx - len, -len); - } + idx += len; + if (idx < 0) { + rb_raise(rb_eIndexError, "index %ld too small for array; minimum: %ld", + idx - len, -len); + } } else if (idx >= ARY_MAX_SIZE) { - rb_raise(rb_eIndexError, "index %ld too big", idx); + rb_raise(rb_eIndexError, "index %ld too big", idx); } rb_ary_modify(ary); if (idx >= ARY_CAPA(ary)) { - ary_double_capa(ary, idx); + ary_double_capa(ary, idx); } if (idx > len) { - ary_mem_clear(ary, len, idx - len + 1); + ary_mem_clear(ary, len, idx - len + 1); } if (idx >= len) { - ARY_SET_LEN(ary, idx + 1); + ARY_SET_LEN(ary, idx + 1); } ARY_SET(ary, idx, val); } @@ -1181,17 +1346,20 @@ ary_make_partial(VALUE ary, VALUE klass, long offset, long len) assert(len >= 0); assert(offset+len <= RARRAY_LEN(ary)); - if (len <= RARRAY_EMBED_LEN_MAX) { - VALUE result = ary_alloc(klass); + const size_t rarray_embed_capa_max = (sizeof(struct RArray) - offsetof(struct RArray, as.ary)) / sizeof(VALUE); + + if ((size_t)len <= rarray_embed_capa_max && ary_embeddable_p(len)) { + VALUE result = ary_alloc_embed(klass, len); ary_memcpy(result, 0, len, RARRAY_CONST_PTR_TRANSIENT(ary) + offset); ARY_SET_EMBED_LEN(result, len); return result; } else { - VALUE shared, result = ary_alloc(klass); - FL_UNSET_EMBED(result); + VALUE shared = ary_make_shared(ary); + + VALUE result = ary_alloc_heap(klass); + assert(!ARY_EMBED_P(result)); - shared = ary_make_shared(ary); ARY_SET_PTR(result, RARRAY_CONST_PTR_TRANSIENT(ary)); ARY_SET_LEN(result, RARRAY_LEN(ary)); rb_ary_set_shared(result, shared); @@ -1216,21 +1384,25 @@ ary_make_partial_step(VALUE ary, VALUE klass, long offset, long len, long step) const VALUE *values = RARRAY_CONST_PTR_TRANSIENT(ary); const long orig_len = len; - if ((step > 0 && step >= len) || (step < 0 && (step < -len))) { + if (step > 0 && step >= len) { VALUE result = ary_new(klass, 1); VALUE *ptr = (VALUE *)ARY_EMBED_PTR(result); RB_OBJ_WRITE(result, ptr, values[offset]); ARY_SET_EMBED_LEN(result, 1); return result; } + else if (step < 0 && step < -len) { + step = -len; + } long ustep = (step < 0) ? -step : step; - len = (len + ustep - 1) / ustep; + len = roomof(len, ustep); long i; long j = offset + ((step > 0) ? 0 : (orig_len - 1)); + VALUE result = ary_new(klass, len); - if (len <= RARRAY_EMBED_LEN_MAX) { + if (ARY_EMBED_P(result)) { VALUE *ptr = (VALUE *)ARY_EMBED_PTR(result); for (i = 0; i < len; ++i) { RB_OBJ_WRITE(result, ptr+i, values[j]); @@ -1279,13 +1451,13 @@ ary_take_first_or_last(int argc, const VALUE *argv, VALUE ary, enum ary_take_pos n = NUM2LONG(argv[0]); len = RARRAY_LEN(ary); if (n > len) { - n = len; + n = len; } else if (n < 0) { - rb_raise(rb_eArgError, "negative array size"); + rb_raise(rb_eArgError, "negative array size"); } if (last) { - offset = len - n; + offset = len - n; } return ary_make_partial(ary, rb_cArray, offset, n); } @@ -1295,13 +1467,16 @@ ary_take_first_or_last(int argc, const VALUE *argv, VALUE ary, enum ary_take_pos * array << object -> self * * Appends +object+ to +self+; returns +self+: + * * a = [:foo, 'bar', 2] * a << :baz # => [:foo, "bar", 2, :baz] * * Appends +object+ as one element, even if it is another \Array: + * * a = [:foo, 'bar', 2] * a1 = a << [3, 4] * a1 # => [:foo, "bar", 2, [3, 4]] + * */ VALUE @@ -1310,7 +1485,7 @@ rb_ary_push(VALUE ary, VALUE item) long idx = RARRAY_LEN((ary_verify(ary), ary)); VALUE target_ary = ary_ensure_room_for_push(ary, 1); RARRAY_PTR_USE_TRANSIENT(ary, ptr, { - RB_OBJ_WRITE(target_ary, &ptr[idx], item); + RB_OBJ_WRITE(target_ary, &ptr[idx], item); }); ARY_SET_LEN(ary, idx + 1); ary_verify(ary); @@ -1334,15 +1509,17 @@ rb_ary_cat(VALUE ary, const VALUE *argv, long len) * Appends trailing elements. * * Appends each argument in +objects+ to +self+; returns +self+: + * * a = [:foo, 'bar', 2] * a.push(:baz, :bat) # => [:foo, "bar", 2, :baz, :bat] * * Appends each argument as one element, even if it is another \Array: + * * a = [:foo, 'bar', 2] * a1 = a.push([:baz, :bat], [:bam, :bad]) * a1 # => [:foo, "bar", 2, [:baz, :bat], [:bam, :bad]] * - * Array#append is an alias for \Array#push. + * Array#append is an alias for Array#push. * * Related: #pop, #shift, #unshift. */ @@ -1361,10 +1538,10 @@ rb_ary_pop(VALUE ary) n = RARRAY_LEN(ary); if (n == 0) return Qnil; if (ARY_OWNS_HEAP_P(ary) && - n * 3 < ARY_CAPA(ary) && - ARY_CAPA(ary) > ARY_DEFAULT_SIZE) + n * 3 < ARY_CAPA(ary) && + ARY_CAPA(ary) > ARY_DEFAULT_SIZE) { - ary_resize_capa(ary, n * 2); + ary_resize_capa(ary, n * 2); } --n; ARY_SET_LEN(ary, n); @@ -1381,6 +1558,7 @@ rb_ary_pop(VALUE ary) * * When no argument is given and +self+ is not empty, * removes and returns the last element: + * * a = [:foo, 'bar', 2] * a.pop # => 2 * a # => [:foo, "bar"] @@ -1388,12 +1566,14 @@ rb_ary_pop(VALUE ary) * Returns +nil+ if the array is empty. * * When a non-negative \Integer argument +n+ is given and is in range, + * * removes and returns the last +n+ elements in a new \Array: * a = [:foo, 'bar', 2] * a.pop(2) # => ["bar", 2] * * If +n+ is positive and out of range, * removes and returns all elements: + * * a = [:foo, 'bar', 2] * a.pop(50) # => [:foo, "bar", 2] * @@ -1406,7 +1586,7 @@ rb_ary_pop_m(int argc, VALUE *argv, VALUE ary) VALUE result; if (argc == 0) { - return rb_ary_pop(ary); + return rb_ary_pop(ary); } rb_ary_modify_check(ary); @@ -1422,30 +1602,14 @@ rb_ary_shift(VALUE ary) VALUE top; long len = RARRAY_LEN(ary); - rb_ary_modify_check(ary); - if (len == 0) return Qnil; - top = RARRAY_AREF(ary, 0); - if (!ARY_SHARED_P(ary)) { - if (len < ARY_DEFAULT_SIZE) { - RARRAY_PTR_USE_TRANSIENT(ary, ptr, { - MEMMOVE(ptr, ptr+1, VALUE, len-1); - }); /* WB: no new reference */ - ARY_INCREASE_LEN(ary, -1); - ary_verify(ary); - return top; - } - assert(!ARY_EMBED_P(ary)); /* ARY_EMBED_LEN_MAX < ARY_DEFAULT_SIZE */ - - ARY_SET(ary, 0, Qnil); - ary_make_shared(ary); - } - else if (ARY_SHARED_ROOT_OCCUPIED(ARY_SHARED_ROOT(ary))) { - RARRAY_PTR_USE_TRANSIENT(ary, ptr, ptr[0] = Qnil); + if (len == 0) { + rb_ary_modify_check(ary); + return Qnil; } - ARY_INCREASE_PTR(ary, 1); /* shift ptr */ - ARY_INCREASE_LEN(ary, -1); - ary_verify(ary); + top = RARRAY_AREF(ary, 0); + + rb_ary_behead(ary, 1); return top; } @@ -1458,6 +1622,7 @@ rb_ary_shift(VALUE ary) * Removes and returns leading elements. * * When no argument is given, removes and returns the first element: + * * a = [:foo, 'bar', 2] * a.shift # => :foo * a # => ['bar', 2] @@ -1466,12 +1631,14 @@ rb_ary_shift(VALUE ary) * * When positive \Integer argument +n+ is given, removes the first +n+ elements; * returns those elements in a new \Array: + * * a = [:foo, 'bar', 2] * a.shift(2) # => [:foo, 'bar'] * a # => [2] * * If +n+ is as large as or larger than <tt>self.length</tt>, * removes all elements; returns those elements in a new \Array: + * * a = [:foo, 'bar', 2] * a.shift(3) # => [:foo, 'bar', 2] * @@ -1487,7 +1654,7 @@ rb_ary_shift_m(int argc, VALUE *argv, VALUE ary) long n; if (argc == 0) { - return rb_ary_shift(ary); + return rb_ary_shift(ary); } rb_ary_modify_check(ary); @@ -1498,48 +1665,37 @@ rb_ary_shift_m(int argc, VALUE *argv, VALUE ary) return result; } -static VALUE -behead_shared(VALUE ary, long n) -{ - assert(ARY_SHARED_P(ary)); - rb_ary_modify_check(ary); - if (ARY_SHARED_ROOT_OCCUPIED(ARY_SHARED_ROOT(ary))) { - ary_mem_clear(ary, 0, n); - } - ARY_INCREASE_PTR(ary, n); - ARY_INCREASE_LEN(ary, -n); - ary_verify(ary); - return ary; -} - -static VALUE -behead_transient(VALUE ary, long n) -{ - rb_ary_modify_check(ary); - RARRAY_PTR_USE_TRANSIENT(ary, ptr, { - MEMMOVE(ptr, ptr+n, VALUE, RARRAY_LEN(ary)-n); - }); /* WB: no new reference */ - ARY_INCREASE_LEN(ary, -n); - ary_verify(ary); - return ary; -} - MJIT_FUNC_EXPORTED VALUE rb_ary_behead(VALUE ary, long n) { if (n <= 0) { return ary; } - else if (ARY_SHARED_P(ary)) { - return behead_shared(ary, n); - } - else if (RARRAY_LEN(ary) >= ARY_DEFAULT_SIZE) { + + rb_ary_modify_check(ary); + + if (!ARY_SHARED_P(ary)) { + if (ARY_EMBED_P(ary) || RARRAY_LEN(ary) < ARY_DEFAULT_SIZE) { + RARRAY_PTR_USE_TRANSIENT(ary, ptr, { + MEMMOVE(ptr, ptr + n, VALUE, RARRAY_LEN(ary) - n); + }); /* WB: no new reference */ + ARY_INCREASE_LEN(ary, -n); + ary_verify(ary); + return ary; + } + + ary_mem_clear(ary, 0, n); ary_make_shared(ary); - return behead_shared(ary, n); } - else { - return behead_transient(ary, n); + else if (ARY_SHARED_ROOT_OCCUPIED(ARY_SHARED_ROOT(ary))) { + ary_mem_clear(ary, 0, n); } + + ARY_INCREASE_PTR(ary, n); + ARY_INCREASE_LEN(ary, -n); + ary_verify(ary); + + return ary; } static VALUE @@ -1570,28 +1726,28 @@ ary_modify_for_unshift(VALUE ary, int argc) rb_ary_modify(ary); capa = ARY_CAPA(ary); if (capa - (capa >> 6) <= new_len) { - ary_double_capa(ary, new_len); + ary_double_capa(ary, new_len); } /* use shared array for big "queues" */ - if (new_len > ARY_DEFAULT_SIZE * 4) { + if (new_len > ARY_DEFAULT_SIZE * 4 && !ARY_EMBED_P(ary)) { ary_verify(ary); /* make a room for unshifted items */ - capa = ARY_CAPA(ary); - ary_make_shared(ary); + capa = ARY_CAPA(ary); + ary_make_shared(ary); head = sharedp = RARRAY_CONST_PTR_TRANSIENT(ary); return make_room_for_unshift(ary, head, (void *)sharedp, argc, capa, len); } else { - /* sliding items */ + /* sliding items */ RARRAY_PTR_USE_TRANSIENT(ary, ptr, { - MEMMOVE(ptr + argc, ptr, VALUE, len); - }); + MEMMOVE(ptr + argc, ptr, VALUE, len); + }); ary_verify(ary); - return ary; + return ary; } } @@ -1632,6 +1788,7 @@ ary_ensure_room_for_unshift(VALUE ary, int argc) * array.unshift(*objects) -> self * * Prepends the given +objects+ to +self+: + * * a = [:foo, 'bar', 2] * a.unshift(:bam, :bat) # => [:bam, :bat, :foo, "bar", 2] * @@ -1647,8 +1804,8 @@ rb_ary_unshift_m(int argc, VALUE *argv, VALUE ary) VALUE target_ary; if (argc == 0) { - rb_ary_modify_check(ary); - return ary; + rb_ary_modify_check(ary); + return ary; } target_ary = ary_ensure_room_for_unshift(ary, argc); @@ -1670,7 +1827,7 @@ rb_ary_elt(VALUE ary, long offset) long len = RARRAY_LEN(ary); if (len == 0) return Qnil; if (offset < 0 || len <= offset) { - return Qnil; + return Qnil; } return RARRAY_AREF(ary, offset); } @@ -1691,7 +1848,7 @@ rb_ary_subseq_step(VALUE ary, long beg, long len, long step) if (beg < 0 || len < 0) return Qnil; if (alen < len || alen < beg + len) { - len = alen - beg; + len = alen - beg; } klass = rb_cArray; if (len == 0) return ary_new(klass, 0); @@ -1725,12 +1882,14 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e); * Returns elements from +self+; does not modify +self+. * * When a single \Integer argument +index+ is given, returns the element at offset +index+: + * * a = [:foo, 'bar', 2] * a[0] # => :foo * a[2] # => 2 * a # => [:foo, "bar", 2] * * If +index+ is negative, counts relative to the end of +self+: + * * a = [:foo, 'bar', 2] * a[-1] # => 2 * a[-2] # => "bar" @@ -1739,12 +1898,14 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e); * * When two \Integer arguments +start+ and +length+ are given, * returns a new \Array of size +length+ containing successive elements beginning at offset +start+: + * * a = [:foo, 'bar', 2] * a[0, 2] # => [:foo, "bar"] * a[1, 2] # => ["bar", 2] * * If <tt>start + length</tt> is greater than <tt>self.length</tt>, * returns all elements from offset +start+ to the end: + * * a = [:foo, 'bar', 2] * a[0, 4] # => [:foo, "bar", 2] * a[1, 3] # => ["bar", 2] @@ -1758,6 +1919,7 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e); * When a single \Range argument +range+ is given, * treats <tt>range.min</tt> as +start+ above * and <tt>range.size</tt> as +length+ above: + * * a = [:foo, 'bar', 2] * a[0..1] # => [:foo, "bar"] * a[1..2] # => ["bar", 2] @@ -1765,31 +1927,36 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e); * Special case: If <tt>range.start == a.size</tt>, returns a new empty \Array. * * If <tt>range.end</tt> is negative, calculates the end index from the end: + * * a = [:foo, 'bar', 2] * a[0..-1] # => [:foo, "bar", 2] * a[0..-2] # => [:foo, "bar"] * a[0..-3] # => [:foo] * * If <tt>range.start</tt> is negative, calculates the start index from the end: + * * a = [:foo, 'bar', 2] * a[-1..2] # => [2] * a[-2..2] # => ["bar", 2] * a[-3..2] # => [:foo, "bar", 2] * * If <tt>range.start</tt> is larger than the array size, returns +nil+. + * * a = [:foo, 'bar', 2] * a[4..1] # => nil * a[4..0] # => nil * a[4..-1] # => nil * * When a single Enumerator::ArithmeticSequence argument +aseq+ is given, - * returns an Array of elements corresponding to the indexes produced by + * returns an \Array of elements corresponding to the indexes produced by * the sequence. + * * a = ['--', 'data1', '--', 'data2', '--', 'data3'] * a[(1..).step(2)] # => ["data1", "data2", "data3"] * * Unlike slicing with range, if the start or the end of the arithmetic sequence * is larger than array size, throws RangeError. + * * a = ['--', 'data1', '--', 'data2', '--', 'data3'] * a[(1..11).step(2)] * # RangeError (((1..11).step(2)) out of range) @@ -1798,6 +1965,7 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e); * * If given a single argument, and its type is not one of the listed, tries to * convert it to Integer, and raises if it is impossible: + * * a = [:foo, 'bar', 2] * # Raises TypeError (no implicit conversion of Symbol into Integer): * a[:foo] @@ -1810,7 +1978,7 @@ rb_ary_aref(int argc, const VALUE *argv, VALUE ary) { rb_check_arity(argc, 1, 2); if (argc == 2) { - return rb_ary_aref2(ary, argv[0], argv[1]); + return rb_ary_aref2(ary, argv[0], argv[1]); } return rb_ary_aref1(ary, argv[0]); } @@ -1821,7 +1989,7 @@ rb_ary_aref2(VALUE ary, VALUE b, VALUE e) long beg = NUM2LONG(b); long len = NUM2LONG(e); if (beg < 0) { - beg += RARRAY_LEN(ary); + beg += RARRAY_LEN(ary); } return rb_ary_subseq(ary, beg, len); } @@ -1833,7 +2001,7 @@ rb_ary_aref1(VALUE ary, VALUE arg) /* special case - speeding up */ if (FIXNUM_P(arg)) { - return rb_ary_entry(ary, FIX2LONG(arg)); + return rb_ary_entry(ary, FIX2LONG(arg)); } /* check if idx is Range or ArithmeticSequence */ switch (rb_arithmetic_sequence_beg_len_step(arg, &beg, &len, &step, RARRAY_LEN(ary), 0)) { @@ -1856,6 +2024,7 @@ rb_ary_aref1(VALUE ary, VALUE arg) * a = [:foo, 'bar', 2] * a.at(0) # => :foo * a.at(2) # => 2 + * */ VALUE @@ -1872,6 +2041,7 @@ rb_ary_at(VALUE ary, VALUE pos) * Returns elements from +self+; does not modify +self+. * * When no argument is given, returns the first element: + * * a = [:foo, 'bar', 2] * a.first # => :foo * a # => [:foo, "bar", 2] @@ -1880,14 +2050,17 @@ rb_ary_at(VALUE ary, VALUE pos) * * When non-negative \Integer argument +n+ is given, * returns the first +n+ elements in a new \Array: + * * a = [:foo, 'bar', 2] * a.first(2) # => [:foo, "bar"] * * If <tt>n >= array.size</tt>, returns all elements: + * * a = [:foo, 'bar', 2] * a.first(50) # => [:foo, "bar", 2] * * If <tt>n == 0</tt> returns an new empty \Array: + * * a = [:foo, 'bar', 2] * a.first(0) # [] * @@ -1897,11 +2070,11 @@ static VALUE rb_ary_first(int argc, VALUE *argv, VALUE ary) { if (argc == 0) { - if (RARRAY_LEN(ary) == 0) return Qnil; - return RARRAY_AREF(ary, 0); + if (RARRAY_LEN(ary) == 0) return Qnil; + return RARRAY_AREF(ary, 0); } else { - return ary_take_first_or_last(argc, argv, ary, ARY_TAKE_FIRST); + return ary_take_first_or_last(argc, argv, ary, ARY_TAKE_FIRST); } } @@ -1913,22 +2086,26 @@ rb_ary_first(int argc, VALUE *argv, VALUE ary) * Returns elements from +self+; +self+ is not modified. * * When no argument is given, returns the last element: + * * a = [:foo, 'bar', 2] * a.last # => 2 * a # => [:foo, "bar", 2] * * If +self+ is empty, returns +nil+. * - * When non-negative \Innteger argument +n+ is given, + * When non-negative \Integer argument +n+ is given, * returns the last +n+ elements in a new \Array: + * * a = [:foo, 'bar', 2] * a.last(2) # => ["bar", 2] * * If <tt>n >= array.size</tt>, returns all elements: + * * a = [:foo, 'bar', 2] * a.last(50) # => [:foo, "bar", 2] * * If <tt>n == 0</tt>, returns an new empty \Array: + * * a = [:foo, 'bar', 2] * a.last(0) # [] * @@ -1939,12 +2116,12 @@ VALUE rb_ary_last(int argc, const VALUE *argv, VALUE ary) { if (argc == 0) { - long len = RARRAY_LEN(ary); - if (len == 0) return Qnil; - return RARRAY_AREF(ary, len-1); + long len = RARRAY_LEN(ary); + if (len == 0) return Qnil; + return RARRAY_AREF(ary, len-1); } else { - return ary_take_first_or_last(argc, argv, ary, ARY_TAKE_LAST); + return ary_take_first_or_last(argc, argv, ary, ARY_TAKE_LAST); } } @@ -1958,10 +2135,12 @@ rb_ary_last(int argc, const VALUE *argv, VALUE ary) * * With the single \Integer argument +index+, * returns the element at offset +index+: + * * a = [:foo, 'bar', 2] * a.fetch(1) # => "bar" * * If +index+ is negative, counts from the end of the array: + * * a = [:foo, 'bar', 2] * a.fetch(-1) # => 2 * a.fetch(-2) # => "bar" @@ -1969,6 +2148,7 @@ rb_ary_last(int argc, const VALUE *argv, VALUE ary) * With arguments +index+ and +default_value+, * returns the element at offset +index+ if index is in range, * otherwise returns +default_value+: + * * a = [:foo, 'bar', 2] * a.fetch(1, nil) # => "bar" * @@ -1979,6 +2159,7 @@ rb_ary_last(int argc, const VALUE *argv, VALUE ary) * a = [:foo, 'bar', 2] * a.fetch(1) {|index| raise 'Cannot happen' } # => "bar" * a.fetch(50) {|index| "Value for #{index}" } # => "Value for 50" + * */ static VALUE @@ -1991,20 +2172,20 @@ rb_ary_fetch(int argc, VALUE *argv, VALUE ary) rb_scan_args(argc, argv, "11", &pos, &ifnone); block_given = rb_block_given_p(); if (block_given && argc == 2) { - rb_warn("block supersedes default value argument"); + rb_warn("block supersedes default value argument"); } idx = NUM2LONG(pos); if (idx < 0) { - idx += RARRAY_LEN(ary); + idx += RARRAY_LEN(ary); } if (idx < 0 || RARRAY_LEN(ary) <= idx) { - if (block_given) return rb_yield(pos); - if (argc == 1) { - rb_raise(rb_eIndexError, "index %ld outside of array bounds: %ld...%ld", - idx - (idx < 0 ? RARRAY_LEN(ary) : 0), -RARRAY_LEN(ary), RARRAY_LEN(ary)); - } - return ifnone; + if (block_given) return rb_yield(pos); + if (argc == 1) { + rb_raise(rb_eIndexError, "index %ld outside of array bounds: %ld...%ld", + idx - (idx < 0 ? RARRAY_LEN(ary) : 0), -RARRAY_LEN(ary), RARRAY_LEN(ary)); + } + return ifnone; } return RARRAY_AREF(ary, idx); } @@ -2020,6 +2201,7 @@ rb_ary_fetch(int argc, VALUE *argv, VALUE ary) * When argument +object+ is given but no block, * returns the index of the first element +element+ * for which <tt>object == element</tt>: + * * a = [:foo, 'bar', 2, 'bar'] * a.index('bar') # => 1 * @@ -2028,12 +2210,14 @@ rb_ary_fetch(int argc, VALUE *argv, VALUE ary) * When both argument +object+ and a block are given, * calls the block with each successive element; * returns the index of the first element for which the block returns a truthy value: + * * a = [:foo, 'bar', 2, 'bar'] * a.index {|element| element == 'bar' } # => 1 * * Returns +nil+ if the block never returns a truthy value. * * When neither an argument nor a block is given, returns a new Enumerator: + * * a = [:foo, 'bar', 2] * e = a.index * e # => #<Enumerator: [:foo, "bar", 2]:index> @@ -2051,23 +2235,23 @@ rb_ary_index(int argc, VALUE *argv, VALUE ary) long i; if (argc == 0) { - RETURN_ENUMERATOR(ary, 0, 0); - for (i=0; i<RARRAY_LEN(ary); i++) { - if (RTEST(rb_yield(RARRAY_AREF(ary, i)))) { - return LONG2NUM(i); - } - } - return Qnil; + RETURN_ENUMERATOR(ary, 0, 0); + for (i=0; i<RARRAY_LEN(ary); i++) { + if (RTEST(rb_yield(RARRAY_AREF(ary, i)))) { + return LONG2NUM(i); + } + } + return Qnil; } rb_check_arity(argc, 0, 1); val = argv[0]; if (rb_block_given_p()) - rb_warn("given block not used"); + rb_warn("given block not used"); for (i=0; i<RARRAY_LEN(ary); i++) { - VALUE e = RARRAY_AREF(ary, i); - if (rb_equal(e, val)) { - return LONG2NUM(i); - } + VALUE e = RARRAY_AREF(ary, i); + if (rb_equal(e, val)) { + return LONG2NUM(i); + } } return Qnil; } @@ -2081,6 +2265,7 @@ rb_ary_index(int argc, VALUE *argv, VALUE ary) * Returns the index of the last element for which <tt>object == element</tt>. * * When argument +object+ is given but no block, returns the index of the last such element found: + * * a = [:foo, 'bar', 2, 'bar'] * a.rindex('bar') # => 3 * @@ -2088,6 +2273,7 @@ rb_ary_index(int argc, VALUE *argv, VALUE ary) * * When a block is given but no argument, calls the block with each successive element; * returns the index of the last element for which the block returns a truthy value: + * * a = [:foo, 'bar', 2, 'bar'] * a.rindex {|element| element == 'bar' } # => 3 * @@ -2110,25 +2296,25 @@ rb_ary_rindex(int argc, VALUE *argv, VALUE ary) long i = RARRAY_LEN(ary), len; if (argc == 0) { - RETURN_ENUMERATOR(ary, 0, 0); - while (i--) { - if (RTEST(rb_yield(RARRAY_AREF(ary, i)))) - return LONG2NUM(i); - if (i > (len = RARRAY_LEN(ary))) { - i = len; - } - } - return Qnil; + RETURN_ENUMERATOR(ary, 0, 0); + while (i--) { + if (RTEST(rb_yield(RARRAY_AREF(ary, i)))) + return LONG2NUM(i); + if (i > (len = RARRAY_LEN(ary))) { + i = len; + } + } + return Qnil; } rb_check_arity(argc, 0, 1); val = argv[0]; if (rb_block_given_p()) - rb_warn("given block not used"); + rb_warn("given block not used"); while (i--) { - VALUE e = RARRAY_AREF(ary, i); - if (rb_equal(e, val)) { - return LONG2NUM(i); - } + VALUE e = RARRAY_AREF(ary, i); + if (rb_equal(e, val)) { + return LONG2NUM(i); + } if (i > RARRAY_LEN(ary)) { break; } @@ -2154,54 +2340,54 @@ rb_ary_splice(VALUE ary, long beg, long len, const VALUE *rptr, long rlen) if (len < 0) rb_raise(rb_eIndexError, "negative length (%ld)", len); olen = RARRAY_LEN(ary); if (beg < 0) { - beg += olen; - if (beg < 0) { - rb_raise(rb_eIndexError, "index %ld too small for array; minimum: %ld", - beg - olen, -olen); - } + beg += olen; + if (beg < 0) { + rb_raise(rb_eIndexError, "index %ld too small for array; minimum: %ld", + beg - olen, -olen); + } } if (olen < len || olen < beg + len) { - len = olen - beg; + len = olen - beg; } { const VALUE *optr = RARRAY_CONST_PTR_TRANSIENT(ary); - rofs = (rptr >= optr && rptr < optr + olen) ? rptr - optr : -1; + rofs = (rptr >= optr && rptr < optr + olen) ? rptr - optr : -1; } if (beg >= olen) { - VALUE target_ary; - if (beg > ARY_MAX_SIZE - rlen) { - rb_raise(rb_eIndexError, "index %ld too big", beg); - } - target_ary = ary_ensure_room_for_push(ary, rlen-len); /* len is 0 or negative */ - len = beg + rlen; - ary_mem_clear(ary, olen, beg - olen); - if (rlen > 0) { + VALUE target_ary; + if (beg > ARY_MAX_SIZE - rlen) { + rb_raise(rb_eIndexError, "index %ld too big", beg); + } + target_ary = ary_ensure_room_for_push(ary, rlen-len); /* len is 0 or negative */ + len = beg + rlen; + ary_mem_clear(ary, olen, beg - olen); + if (rlen > 0) { if (rofs != -1) rptr = RARRAY_CONST_PTR_TRANSIENT(ary) + rofs; - ary_memcpy0(ary, beg, rlen, rptr, target_ary); - } - ARY_SET_LEN(ary, len); + ary_memcpy0(ary, beg, rlen, rptr, target_ary); + } + ARY_SET_LEN(ary, len); } else { - long alen; - - if (olen - len > ARY_MAX_SIZE - rlen) { - rb_raise(rb_eIndexError, "index %ld too big", olen + rlen - len); - } - rb_ary_modify(ary); - alen = olen + rlen - len; - if (alen >= ARY_CAPA(ary)) { - ary_double_capa(ary, alen); - } - - if (len != rlen) { + long alen; + + if (olen - len > ARY_MAX_SIZE - rlen) { + rb_raise(rb_eIndexError, "index %ld too big", olen + rlen - len); + } + rb_ary_modify(ary); + alen = olen + rlen - len; + if (alen >= ARY_CAPA(ary)) { + ary_double_capa(ary, alen); + } + + if (len != rlen) { RARRAY_PTR_USE_TRANSIENT(ary, ptr, MEMMOVE(ptr + beg + rlen, ptr + beg + len, VALUE, olen - (beg + len))); - ARY_SET_LEN(ary, alen); - } - if (rlen > 0) { + ARY_SET_LEN(ary, alen); + } + if (rlen > 0) { if (rofs != -1) rptr = RARRAY_CONST_PTR_TRANSIENT(ary) + rofs; /* give up wb-protected ary */ RB_OBJ_WB_UNPROTECT_FOR(ARRAY, ary); @@ -2211,7 +2397,7 @@ rb_ary_splice(VALUE ary, long beg, long len, const VALUE *rptr, long rlen) */ RARRAY_PTR_USE_TRANSIENT(ary, ptr, MEMMOVE(ptr + beg, rptr, VALUE, rlen)); - } + } } } @@ -2222,10 +2408,10 @@ rb_ary_set_len(VALUE ary, long len) rb_ary_modify_check(ary); if (ARY_SHARED_P(ary)) { - rb_raise(rb_eRuntimeError, "can't set length of shared "); + rb_raise(rb_eRuntimeError, "can't set length of shared "); } if (len > (capa = (long)ARY_CAPA(ary))) { - rb_bug("probable buffer overflow: %ld for %ld", len, capa); + rb_bug("probable buffer overflow: %ld for %ld", len, capa); } ARY_SET_LEN(ary, len); } @@ -2239,31 +2425,37 @@ rb_ary_resize(VALUE ary, long len) olen = RARRAY_LEN(ary); if (len == olen) return ary; if (len > ARY_MAX_SIZE) { - rb_raise(rb_eIndexError, "index %ld too big", len); + rb_raise(rb_eIndexError, "index %ld too big", len); } if (len > olen) { - if (len >= ARY_CAPA(ary)) { - ary_double_capa(ary, len); - } - ary_mem_clear(ary, olen, len - olen); - ARY_SET_LEN(ary, len); + if (len >= ARY_CAPA(ary)) { + ary_double_capa(ary, len); + } + ary_mem_clear(ary, olen, len - olen); + ARY_SET_LEN(ary, len); } else if (ARY_EMBED_P(ary)) { ARY_SET_EMBED_LEN(ary, len); } - else if (len <= RARRAY_EMBED_LEN_MAX) { - VALUE tmp[RARRAY_EMBED_LEN_MAX]; - MEMCPY(tmp, ARY_HEAP_PTR(ary), VALUE, len); - ary_discard(ary); - MEMCPY((VALUE *)ARY_EMBED_PTR(ary), tmp, VALUE, len); /* WB: no new reference */ + else if (len <= ary_embed_capa(ary)) { + const VALUE *ptr = ARY_HEAP_PTR(ary); + long ptr_capa = ARY_HEAP_SIZE(ary); + bool is_malloc_ptr = !ARY_SHARED_P(ary) && !RARRAY_TRANSIENT_P(ary); + + FL_UNSET(ary, RARRAY_TRANSIENT_FLAG); + FL_SET_EMBED(ary); + + MEMCPY((VALUE *)ARY_EMBED_PTR(ary), ptr, VALUE, len); /* WB: no new reference */ ARY_SET_EMBED_LEN(ary, len); + + if (is_malloc_ptr) ruby_sized_xfree((void *)ptr, ptr_capa); } else { - if (olen > len + ARY_DEFAULT_SIZE) { + if (olen > len + ARY_DEFAULT_SIZE) { size_t new_capa = ary_heap_realloc(ary, len); ARY_SET_CAPA(ary, new_capa); - } - ARY_SET_HEAP_LEN(ary, len); + } + ARY_SET_HEAP_LEN(ary, len); } ary_verify(ary); return ary; @@ -2296,16 +2488,19 @@ ary_aset_by_rb_ary_splice(VALUE ary, long beg, long len, VALUE val) * When \Integer argument +index+ is given, assigns +object+ to an element in +self+. * * If +index+ is non-negative, assigns +object+ the element at offset +index+: + * * a = [:foo, 'bar', 2] * a[0] = 'foo' # => "foo" * a # => ["foo", "bar", 2] * * If +index+ is greater than <tt>self.length</tt>, extends the array: + * * a = [:foo, 'bar', 2] * a[7] = 'foo' # => "foo" * a # => [:foo, "bar", 2, nil, nil, nil, nil, "foo"] * * If +index+ is negative, counts backwards from the end of the array: + * * a = [:foo, 'bar', 2] * a[-1] = 'two' # => "two" * a # => [:foo, "bar", "two"] @@ -2313,11 +2508,13 @@ ary_aset_by_rb_ary_splice(VALUE ary, long beg, long len, VALUE val) * When \Integer arguments +start+ and +length+ are given and +object+ is not an \Array, * removes <tt>length - 1</tt> elements beginning at offset +start+, * and assigns +object+ at offset +start+: + * * a = [:foo, 'bar', 2] * a[0, 2] = 'foo' # => "foo" * a # => ["foo", 2] * * If +start+ is negative, counts backwards from the end of the array: + * * a = [:foo, 'bar', 2] * a[-2, 2] = 'foo' # => "foo" * a # => [:foo, "foo"] @@ -2325,17 +2522,20 @@ ary_aset_by_rb_ary_splice(VALUE ary, long beg, long len, VALUE val) * If +start+ is non-negative and outside the array (<tt> >= self.size</tt>), * extends the array with +nil+, assigns +object+ at offset +start+, * and ignores +length+: + * * a = [:foo, 'bar', 2] * a[6, 50] = 'foo' # => "foo" * a # => [:foo, "bar", 2, nil, nil, nil, "foo"] * * If +length+ is zero, shifts elements at and following offset +start+ * and assigns +object+ at offset +start+: + * * a = [:foo, 'bar', 2] * a[1, 0] = 'foo' # => "foo" * a # => [:foo, "foo", "bar", 2] * * If +length+ is too large for the existing array, does not extend the array: + * * a = [:foo, 'bar', 2] * a[1, 5] = 'foo' # => "foo" * a # => [:foo, "foo"] @@ -2343,29 +2543,34 @@ ary_aset_by_rb_ary_splice(VALUE ary, long beg, long len, VALUE val) * When \Range argument +range+ is given and +object+ is an \Array, * removes <tt>length - 1</tt> elements beginning at offset +start+, * and assigns +object+ at offset +start+: + * * a = [:foo, 'bar', 2] * a[0..1] = 'foo' # => "foo" * a # => ["foo", 2] * * if <tt>range.begin</tt> is negative, counts backwards from the end of the array: + * * a = [:foo, 'bar', 2] * a[-2..2] = 'foo' # => "foo" * a # => [:foo, "foo"] * * If the array length is less than <tt>range.begin</tt>, * assigns +object+ at offset <tt>range.begin</tt>, and ignores +length+: + * * a = [:foo, 'bar', 2] * a[6..50] = 'foo' # => "foo" * a # => [:foo, "bar", 2, nil, nil, nil, "foo"] * * If <tt>range.end</tt> is zero, shifts elements at and following offset +start+ * and assigns +object+ at offset +start+: + * * a = [:foo, 'bar', 2] * a[1..0] = 'foo' # => "foo" * a # => [:foo, "foo", "bar", 2] * * If <tt>range.end</tt> is negative, assigns +object+ at offset +start+, * retains <tt>range.end.abs -1</tt> elements past that, and removes those beyond: + * * a = [:foo, 'bar', 2] * a[1..-1] = 'foo' # => "foo" * a # => [:foo, "foo"] @@ -2379,9 +2584,11 @@ ary_aset_by_rb_ary_splice(VALUE ary, long beg, long len, VALUE val) * * If <tt>range.end</tt> is too large for the existing array, * replaces array elements, but does not extend the array with +nil+ values: + * * a = [:foo, 'bar', 2] * a[1..5] = 'foo' # => "foo" * a # => [:foo, "foo"] + * */ static VALUE @@ -2392,16 +2599,16 @@ rb_ary_aset(int argc, VALUE *argv, VALUE ary) rb_check_arity(argc, 2, 3); rb_ary_modify_check(ary); if (argc == 3) { - beg = NUM2LONG(argv[0]); - len = NUM2LONG(argv[1]); + beg = NUM2LONG(argv[0]); + len = NUM2LONG(argv[1]); return ary_aset_by_rb_ary_splice(ary, beg, len, argv[2]); } if (FIXNUM_P(argv[0])) { - offset = FIX2LONG(argv[0]); + offset = FIX2LONG(argv[0]); return ary_aset_by_rb_ary_store(ary, offset, argv[1]); } if (rb_range_beg_len(argv[0], &beg, &len, RARRAY_LEN(ary), 1)) { - /* check if idx is Range */ + /* check if idx is Range */ return ary_aset_by_rb_ary_splice(ary, beg, len, argv[1]); } @@ -2418,15 +2625,18 @@ rb_ary_aset(int argc, VALUE *argv, VALUE ary) * * When +index+ is non-negative, inserts all given +objects+ * before the element at offset +index+: + * * a = [:foo, 'bar', 2] * a.insert(1, :bat, :bam) # => [:foo, :bat, :bam, "bar", 2] * * Extends the array if +index+ is beyond the array (<tt>index >= self.size</tt>): + * * a = [:foo, 'bar', 2] * a.insert(5, :bat, :bam) * a # => [:foo, "bar", 2, nil, nil, :bat, :bam] * * Does nothing if no objects given: + * * a = [:foo, 'bar', 2] * a.insert(1) * a.insert(50) @@ -2435,9 +2645,11 @@ rb_ary_aset(int argc, VALUE *argv, VALUE ary) * * When +index+ is negative, inserts all given +objects+ * _after_ the element at offset <tt>index+self.size</tt>: + * * a = [:foo, 'bar', 2] * a.insert(-2, :bat, :bam) * a # => [:foo, "bar", :bat, :bam, 2] + * */ static VALUE @@ -2450,15 +2662,15 @@ rb_ary_insert(int argc, VALUE *argv, VALUE ary) pos = NUM2LONG(argv[0]); if (argc == 1) return ary; if (pos == -1) { - pos = RARRAY_LEN(ary); + pos = RARRAY_LEN(ary); } else if (pos < 0) { - long minpos = -RARRAY_LEN(ary) - 1; - if (pos < minpos) { - rb_raise(rb_eIndexError, "index %ld too small for array; minimum: %ld", - pos, minpos); - } - pos++; + long minpos = -RARRAY_LEN(ary) - 1; + if (pos < minpos) { + rb_raise(rb_eIndexError, "index %ld too small for array; minimum: %ld", + pos, minpos); + } + pos++; } rb_ary_splice(ary, pos, 0, argv + 1, argc - 1); return ary; @@ -2482,29 +2694,35 @@ ary_enum_length(VALUE ary, VALUE args, VALUE eobj) * * When a block given, passes each successive array element to the block; * returns +self+: + * * a = [:foo, 'bar', 2] * a.each {|element| puts "#{element.class} #{element}" } * * Output: + * * Symbol foo * String bar * Integer 2 * * Allows the array to be modified during iteration: + * * a = [:foo, 'bar', 2] * a.each {|element| puts element; a.clear if element.to_s.start_with?('b') } * * Output: + * * foo * bar * * When no block given, returns a new \Enumerator: * a = [:foo, 'bar', 2] + * * e = a.each * e # => #<Enumerator: [:foo, "bar", 2]:each> * a1 = e.each {|element| puts "#{element.class} #{element}" } * * Output: + * * Symbol foo * String bar * Integer 2 @@ -2519,7 +2737,7 @@ rb_ary_each(VALUE ary) ary_verify(ary); RETURN_SIZED_ENUMERATOR(ary, 0, 0, ary_enum_length); for (i=0; i<RARRAY_LEN(ary); i++) { - rb_yield(RARRAY_AREF(ary, i)); + rb_yield(RARRAY_AREF(ary, i)); } return ary; } @@ -2533,29 +2751,35 @@ rb_ary_each(VALUE ary) * * When a block given, passes each successive array index to the block; * returns +self+: + * * a = [:foo, 'bar', 2] * a.each_index {|index| puts "#{index} #{a[index]}" } * * Output: + * * 0 foo * 1 bar * 2 2 * * Allows the array to be modified during iteration: + * * a = [:foo, 'bar', 2] * a.each_index {|index| puts index; a.clear if index > 0 } * * Output: + * * 0 * 1 * * When no block given, returns a new \Enumerator: + * * a = [:foo, 'bar', 2] * e = a.each_index * e # => #<Enumerator: [:foo, "bar", 2]:each_index> * a1 = e.each {|index| puts "#{index} #{a[index]}"} * * Output: + * * 0 foo * 1 bar * 2 2 @@ -2570,7 +2794,7 @@ rb_ary_each_index(VALUE ary) RETURN_SIZED_ENUMERATOR(ary, 0, 0, ary_enum_length); for (i=0; i<RARRAY_LEN(ary); i++) { - rb_yield(LONG2NUM(i)); + rb_yield(LONG2NUM(i)); } return ary; } @@ -2584,28 +2808,35 @@ rb_ary_each_index(VALUE ary) * * When a block given, passes, in reverse order, each element to the block; * returns +self+: + * * a = [:foo, 'bar', 2] * a.reverse_each {|element| puts "#{element.class} #{element}" } * * Output: + * * Integer 2 * String bar * Symbol foo * * Allows the array to be modified during iteration: + * * a = [:foo, 'bar', 2] * a.reverse_each {|element| puts element; a.clear if element.to_s.start_with?('b') } * * Output: + * * 2 * bar * * When no block given, returns a new \Enumerator: + * * a = [:foo, 'bar', 2] * e = a.reverse_each * e # => #<Enumerator: [:foo, "bar", 2]:reverse_each> * a1 = e.each {|element| puts "#{element.class} #{element}" } + * * Output: + * * Integer 2 * String bar * Symbol foo @@ -2621,12 +2852,12 @@ rb_ary_reverse_each(VALUE ary) RETURN_SIZED_ENUMERATOR(ary, 0, 0, ary_enum_length); len = RARRAY_LEN(ary); while (len--) { - long nlen; - rb_yield(RARRAY_AREF(ary, len)); - nlen = RARRAY_LEN(ary); - if (nlen < len) { - len = nlen; - } + long nlen; + rb_yield(RARRAY_AREF(ary, len)); + nlen = RARRAY_LEN(ary); + if (nlen < len) { + len = nlen; + } } return ary; } @@ -2692,10 +2923,10 @@ recursive_join(VALUE obj, VALUE argp, int recur) int *first = (int *)arg[3]; if (recur) { - rb_raise(rb_eArgError, "recursive array join"); + rb_raise(rb_eArgError, "recursive array join"); } else { - ary_join_1(obj, ary, sep, 0, result, first); + ary_join_1(obj, ary, sep, 0, result, first); } return Qnil; } @@ -2708,11 +2939,11 @@ ary_join_0(VALUE ary, VALUE sep, long max, VALUE result) if (max > 0) rb_enc_copy(result, RARRAY_AREF(ary, 0)); for (i=0; i<max; i++) { - val = RARRAY_AREF(ary, i); + val = RARRAY_AREF(ary, i); if (!RB_TYPE_P(val, T_STRING)) break; - if (i > 0 && !NIL_P(sep)) - rb_str_buf_append(result, sep); - rb_str_buf_append(result, val); + if (i > 0 && !NIL_P(sep)) + rb_str_buf_append(result, sep); + rb_str_buf_append(result, val); } return i; } @@ -2751,16 +2982,16 @@ ary_join_1(VALUE obj, VALUE ary, VALUE sep, long i, VALUE result, int *first) VALUE val, tmp; for (; i<RARRAY_LEN(ary); i++) { - if (i > 0 && !NIL_P(sep)) - rb_str_buf_append(result, sep); + if (i > 0 && !NIL_P(sep)) + rb_str_buf_append(result, sep); - val = RARRAY_AREF(ary, i); - if (RB_TYPE_P(val, T_STRING)) { + val = RARRAY_AREF(ary, i); + if (RB_TYPE_P(val, T_STRING)) { ary_join_1_str(result, val, first); - } - else if (RB_TYPE_P(val, T_ARRAY)) { + } + else if (RB_TYPE_P(val, T_ARRAY)) { ary_join_1_ary(val, ary, sep, result, val, first); - } + } else if (!NIL_P(tmp = rb_check_string_type(val))) { ary_join_1_str(result, tmp, first); } @@ -2769,7 +3000,7 @@ ary_join_1(VALUE obj, VALUE ary, VALUE sep, long i, VALUE result, int *first) } else { ary_join_1_str(result, rb_obj_as_string(val), first); - } + } } } @@ -2782,26 +3013,26 @@ rb_ary_join(VALUE ary, VALUE sep) if (RARRAY_LEN(ary) == 0) return rb_usascii_str_new(0, 0); if (!NIL_P(sep)) { - StringValue(sep); - len += RSTRING_LEN(sep) * (RARRAY_LEN(ary) - 1); + StringValue(sep); + len += RSTRING_LEN(sep) * (RARRAY_LEN(ary) - 1); } for (i=0; i<RARRAY_LEN(ary); i++) { - val = RARRAY_AREF(ary, i); - tmp = rb_check_string_type(val); + val = RARRAY_AREF(ary, i); + tmp = rb_check_string_type(val); - if (NIL_P(tmp) || tmp != val) { - int first; + if (NIL_P(tmp) || tmp != val) { + int first; long n = RARRAY_LEN(ary); if (i > n) i = n; result = rb_str_buf_new(len + (n-i)*10); - rb_enc_associate(result, rb_usascii_encoding()); + rb_enc_associate(result, rb_usascii_encoding()); i = ary_join_0(ary, sep, i, result); - first = i == 0; - ary_join_1(ary, ary, sep, i, result, &first); - return result; - } + first = i == 0; + ary_join_1(ary, ary, sep, i, result, &first); + return result; + } - len += RSTRING_LEN(tmp); + len += RSTRING_LEN(tmp); } result = rb_str_new(0, len); @@ -2818,22 +3049,27 @@ rb_ary_join(VALUE ary, VALUE sep) * array.join(separator = $,) -> new_string * * Returns the new \String formed by joining the array elements after conversion. - * For each element +element+ + * For each element +element+: + * * - Uses <tt>element.to_s</tt> if +element+ is not a <tt>kind_of?(Array)</tt>. * - Uses recursive <tt>element.join(separator)</tt> if +element+ is a <tt>kind_of?(Array)</tt>. * * With no argument, joins using the output field separator, <tt>$,</tt>: + * * a = [:foo, 'bar', 2] * $, # => nil * a.join # => "foobar2" * * With \string argument +separator+, joins using that separator: + * * a = [:foo, 'bar', 2] * a.join("\n") # => "foo\nbar\n2" * * Joins recursively for nested Arrays: + * * a = [:foo, [:bar, [:baz, :bat]]] * a.join # => "foobarbazbat" + * */ static VALUE rb_ary_join_m(int argc, VALUE *argv, VALUE ary) @@ -2859,10 +3095,10 @@ inspect_ary(VALUE ary, VALUE dummy, int recur) if (recur) return rb_usascii_str_new_cstr("[...]"); str = rb_str_buf_new2("["); for (i=0; i<RARRAY_LEN(ary); i++) { - s = rb_inspect(RARRAY_AREF(ary, i)); - if (i > 0) rb_str_buf_cat2(str, ", "); - else rb_enc_copy(str, s); - rb_str_buf_append(str, s); + s = rb_inspect(RARRAY_AREF(ary, i)); + if (i > 0) rb_str_buf_cat2(str, ", "); + else rb_enc_copy(str, s); + rb_str_buf_append(str, s); } rb_str_buf_cat2(str, "]"); return str; @@ -2874,6 +3110,7 @@ inspect_ary(VALUE ary, VALUE dummy, int recur) * * Returns the new \String formed by calling method <tt>#inspect</tt> * on each array element: + * * a = [:foo, 'bar', 2] * a.inspect # => "[:foo, \"bar\", 2]" * @@ -2898,10 +3135,12 @@ rb_ary_to_s(VALUE ary) * to_a -> self or new_array * * When +self+ is an instance of \Array, returns +self+: + * * a = [:foo, 'bar', 2] * a.to_a # => [:foo, "bar", 2] * * Otherwise, returns a new \Array containing the elements of +self+: + * * class MyArray < Array; end * a = MyArray.new(['foo', 'bar', 'two']) * a.instance_of?(Array) # => false @@ -2909,15 +3148,16 @@ rb_ary_to_s(VALUE ary) * a1 = a.to_a * a1 # => ["foo", "bar", "two"] * a1.class # => Array # Not MyArray + * */ static VALUE rb_ary_to_a(VALUE ary) { if (rb_obj_class(ary) != rb_cArray) { - VALUE dup = rb_ary_new2(RARRAY_LEN(ary)); - rb_ary_replace(dup, ary); - return dup; + VALUE dup = rb_ary_new2(RARRAY_LEN(ary)); + rb_ary_replace(dup, ary); + return dup; } return ary; } @@ -2932,16 +3172,19 @@ rb_ary_to_a(VALUE ary) * When a block is given, calls the block with each array element; * the block must return a 2-element \Array whose two elements * form a key-value pair in the returned \Hash: + * * a = ['foo', :bar, 1, [2, 3], {baz: 4}] * h = a.to_h {|item| [item, item] } * h # => {"foo"=>"foo", :bar=>:bar, 1=>1, [2, 3]=>[2, 3], {:baz=>4}=>{:baz=>4}} * * When no block is given, +self+ must be an \Array of 2-element sub-arrays, * each sub-array is formed into a key-value pair in the new \Hash: + * * [].to_h # => {} * a = [['foo', 'zero'], ['bar', 'one'], ['baz', 'two']] * h = a.to_h * h # => {"foo"=>"zero", "bar"=>"one", "baz"=>"two"} + * */ static VALUE @@ -2952,18 +3195,18 @@ rb_ary_to_h(VALUE ary) int block_given = rb_block_given_p(); for (i=0; i<RARRAY_LEN(ary); i++) { - const VALUE e = rb_ary_elt(ary, i); - const VALUE elt = block_given ? rb_yield_force_blockarg(e) : e; - const VALUE key_value_pair = rb_check_array_type(elt); - if (NIL_P(key_value_pair)) { - rb_raise(rb_eTypeError, "wrong element type %"PRIsVALUE" at %ld (expected array)", - rb_obj_class(elt), i); - } - if (RARRAY_LEN(key_value_pair) != 2) { - rb_raise(rb_eArgError, "wrong array length at %ld (expected 2, was %ld)", - i, RARRAY_LEN(key_value_pair)); - } - rb_hash_aset(hash, RARRAY_AREF(key_value_pair, 0), RARRAY_AREF(key_value_pair, 1)); + const VALUE e = rb_ary_elt(ary, i); + const VALUE elt = block_given ? rb_yield_force_blockarg(e) : e; + const VALUE key_value_pair = rb_check_array_type(elt); + if (NIL_P(key_value_pair)) { + rb_raise(rb_eTypeError, "wrong element type %"PRIsVALUE" at %ld (expected array)", + rb_obj_class(elt), i); + } + if (RARRAY_LEN(key_value_pair) != 2) { + rb_raise(rb_eArgError, "wrong array length at %ld (expected 2, was %ld)", + i, RARRAY_LEN(key_value_pair)); + } + rb_hash_aset(hash, RARRAY_AREF(key_value_pair, 0), RARRAY_AREF(key_value_pair, 1)); } return hash; } @@ -2985,9 +3228,9 @@ static void ary_reverse(VALUE *p1, VALUE *p2) { while (p1 < p2) { - VALUE tmp = *p1; - *p1++ = *p2; - *p2-- = tmp; + VALUE tmp = *p1; + *p1++ = *p2; + *p2-- = tmp; } } @@ -3002,7 +3245,7 @@ rb_ary_reverse(VALUE ary) RARRAY_PTR_USE_TRANSIENT(ary, p1, { p2 = p1 + len - 1; /* points last item */ ary_reverse(p1, p2); - }); /* WB: no new reference */ + }); /* WB: no new reference */ } return ary; } @@ -3012,8 +3255,10 @@ rb_ary_reverse(VALUE ary) * array.reverse! -> self * * Reverses +self+ in place: + * * a = ['foo', 'bar', 'two'] * a.reverse! # => ["two", "bar", "foo"] + * */ static VALUE @@ -3026,10 +3271,12 @@ rb_ary_reverse_bang(VALUE ary) * call-seq: * array.reverse -> new_array * - * Returns a new \Array with the elements of +self+ in reverse order. + * Returns a new \Array with the elements of +self+ in reverse order: + * * a = ['foo', 'bar', 'two'] * a1 = a.reverse * a1 # => ["two", "bar", "foo"] + * */ static VALUE @@ -3041,7 +3288,7 @@ rb_ary_reverse_m(VALUE ary) if (len > 0) { const VALUE *p1 = RARRAY_CONST_PTR_TRANSIENT(ary); VALUE *p2 = (VALUE *)RARRAY_CONST_PTR_TRANSIENT(dup) + len - 1; - do *p2-- = *p1++; while (--len > 0); + do *p2-- = *p1++; while (--len > 0); } ARY_SET_LEN(dup, RARRAY_LEN(ary)); return dup; @@ -3097,35 +3344,42 @@ rb_ary_rotate(VALUE ary, long cnt) * Rotates +self+ in place by moving elements from one end to the other; returns +self+. * * When no argument given, rotates the first element to the last position: + * * a = [:foo, 'bar', 2, 'bar'] * a.rotate! # => ["bar", 2, "bar", :foo] * * When given a non-negative \Integer +count+, * rotates +count+ elements from the beginning to the end: + * * a = [:foo, 'bar', 2] * a.rotate!(2) * a # => [2, :foo, "bar"] * * If +count+ is large, uses <tt>count % array.size</tt> as the count: + * * a = [:foo, 'bar', 2] * a.rotate!(20) * a # => [2, :foo, "bar"] * * If +count+ is zero, returns +self+ unmodified: + * * a = [:foo, 'bar', 2] * a.rotate!(0) * a # => [:foo, "bar", 2] * * When given a negative Integer +count+, rotates in the opposite direction, * from end to beginning: + * * a = [:foo, 'bar', 2] * a.rotate!(-2) * a # => ["bar", 2, :foo] * * If +count+ is small (far from zero), uses <tt>count % array.size</tt> as the count: + * * a = [:foo, 'bar', 2] * a.rotate!(-5) * a # => ["bar", 2, :foo] + * */ static VALUE @@ -3146,36 +3400,43 @@ rb_ary_rotate_bang(int argc, VALUE *argv, VALUE ary) * * When no argument given, returns a new \Array that is like +self+, * except that the first element has been rotated to the last position: + * * a = [:foo, 'bar', 2, 'bar'] * a1 = a.rotate * a1 # => ["bar", 2, "bar", :foo] * * When given a non-negative \Integer +count+, * returns a new \Array with +count+ elements rotated from the beginning to the end: + * * a = [:foo, 'bar', 2] * a1 = a.rotate(2) * a1 # => [2, :foo, "bar"] * * If +count+ is large, uses <tt>count % array.size</tt> as the count: + * * a = [:foo, 'bar', 2] * a1 = a.rotate(20) * a1 # => [2, :foo, "bar"] * * If +count+ is zero, returns a copy of +self+, unmodified: + * * a = [:foo, 'bar', 2] * a1 = a.rotate(0) * a1 # => [:foo, "bar", 2] * * When given a negative \Integer +count+, rotates in the opposite direction, * from end to beginning: + * * a = [:foo, 'bar', 2] * a1 = a.rotate(-2) * a1 # => ["bar", 2, :foo] * * If +count+ is small (far from zero), uses <tt>count % array.size</tt> as the count: + * * a = [:foo, 'bar', 2] * a1 = a.rotate(-5) * a1 # => ["bar", 2, :foo] + * */ static VALUE @@ -3189,11 +3450,11 @@ rb_ary_rotate_m(int argc, VALUE *argv, VALUE ary) len = RARRAY_LEN(ary); rotated = rb_ary_new2(len); if (len > 0) { - cnt = rotate_count(cnt, len); + cnt = rotate_count(cnt, len); ptr = RARRAY_CONST_PTR_TRANSIENT(ary); - len -= cnt; - ary_memcpy(rotated, 0, len, ptr + cnt); - ary_memcpy(rotated, len, cnt, ptr); + len -= cnt; + ary_memcpy(rotated, 0, len, ptr + cnt); + ary_memcpy(rotated, len, cnt, ptr); } ARY_SET_LEN(rotated, RARRAY_LEN(ary)); return rotated; @@ -3202,14 +3463,13 @@ rb_ary_rotate_m(int argc, VALUE *argv, VALUE ary) struct ary_sort_data { VALUE ary; VALUE receiver; - struct cmp_opt_data cmp_opt; }; static VALUE sort_reentered(VALUE ary) { if (RBASIC(ary)->klass) { - rb_raise(rb_eRuntimeError, "sort reentered"); + rb_raise(rb_eRuntimeError, "sort reentered"); } return Qnil; } @@ -3248,16 +3508,16 @@ sort_2(const void *ap, const void *bp, void *dummy) VALUE a = *(const VALUE *)ap, b = *(const VALUE *)bp; int n; - if (FIXNUM_P(a) && FIXNUM_P(b) && CMP_OPTIMIZABLE(data->cmp_opt, Integer)) { - if ((long)a > (long)b) return 1; - if ((long)a < (long)b) return -1; - return 0; + if (FIXNUM_P(a) && FIXNUM_P(b) && CMP_OPTIMIZABLE(INTEGER)) { + if ((long)a > (long)b) return 1; + if ((long)a < (long)b) return -1; + return 0; } - if (STRING_P(a) && STRING_P(b) && CMP_OPTIMIZABLE(data->cmp_opt, String)) { - return rb_str_cmp(a, b); + if (STRING_P(a) && STRING_P(b) && CMP_OPTIMIZABLE(STRING)) { + return rb_str_cmp(a, b); } - if (RB_FLOAT_TYPE_P(a) && CMP_OPTIMIZABLE(data->cmp_opt, Float)) { - return rb_float_cmp(a, b); + if (RB_FLOAT_TYPE_P(a) && CMP_OPTIMIZABLE(FLOAT)) { + return rb_float_cmp(a, b); } retval = rb_funcallv(a, id_cmp, 1, &b); @@ -3276,6 +3536,7 @@ sort_2(const void *ap, const void *bp, void *dummy) * * With no block, compares elements using operator <tt><=></tt> * (see Comparable): + * * a = 'abcde'.split('').shuffle * a # => ["e", "b", "d", "a", "c"] * a.sort! @@ -3283,11 +3544,13 @@ sort_2(const void *ap, const void *bp, void *dummy) * * With a block, calls the block with each element pair; * for each element pair +a+ and +b+, the block should return an integer: + * * - Negative when +b+ is to follow +a+. * - Zero when +a+ and +b+ are equivalent. * - Positive when +a+ is to follow +b+. * * Example: + * * a = 'abcde'.split('').shuffle * a # => ["e", "b", "d", "a", "c"] * a.sort! {|a, b| a <=> b } @@ -3297,10 +3560,12 @@ sort_2(const void *ap, const void *bp, void *dummy) * * When the block returns zero, the order for +a+ and +b+ is indeterminate, * and may be unstable: + * * a = 'abcde'.split('').shuffle * a # => ["e", "b", "d", "a", "c"] * a.sort! {|a, b| 0 } * a # => ["d", "e", "c", "a", "b"] + * */ VALUE @@ -3309,25 +3574,23 @@ rb_ary_sort_bang(VALUE ary) rb_ary_modify(ary); assert(!ARY_SHARED_P(ary)); if (RARRAY_LEN(ary) > 1) { - VALUE tmp = ary_make_substitution(ary); /* only ary refers tmp */ - struct ary_sort_data data; - long len = RARRAY_LEN(ary); - RBASIC_CLEAR_CLASS(tmp); - data.ary = tmp; + VALUE tmp = ary_make_substitution(ary); /* only ary refers tmp */ + struct ary_sort_data data; + long len = RARRAY_LEN(ary); + RBASIC_CLEAR_CLASS(tmp); + data.ary = tmp; data.receiver = ary; - data.cmp_opt.opt_methods = 0; - data.cmp_opt.opt_inited = 0; - RARRAY_PTR_USE(tmp, ptr, { + RARRAY_PTR_USE(tmp, ptr, { ruby_qsort(ptr, len, sizeof(VALUE), rb_block_given_p()?sort_1:sort_2, &data); - }); /* WB: no new reference */ - rb_ary_modify(ary); + }); /* WB: no new reference */ + rb_ary_modify(ary); if (ARY_EMBED_P(tmp)) { if (ARY_SHARED_P(ary)) { /* ary might be destructively operated in the given block */ rb_ary_unshare(ary); - FL_SET_EMBED(ary); + FL_SET_EMBED(ary); } - ary_memcpy(ary, 0, ARY_EMBED_LEN(tmp), ARY_EMBED_PTR(tmp)); + ary_memcpy(ary, 0, ARY_EMBED_LEN(tmp), ARY_EMBED_PTR(tmp)); ARY_SET_LEN(ary, ARY_EMBED_LEN(tmp)); } else { @@ -3373,6 +3636,7 @@ rb_ary_sort_bang(VALUE ary) * * With no block, compares elements using operator <tt><=></tt> * (see Comparable): + * * a = 'abcde'.split('').shuffle * a # => ["e", "b", "d", "a", "c"] * a1 = a.sort @@ -3380,11 +3644,13 @@ rb_ary_sort_bang(VALUE ary) * * With a block, calls the block with each element pair; * for each element pair +a+ and +b+, the block should return an integer: + * * - Negative when +b+ is to follow +a+. * - Zero when +a+ and +b+ are equivalent. * - Positive when +a+ is to follow +b+. * * Example: + * * a = 'abcde'.split('').shuffle * a # => ["e", "b", "d", "a", "c"] * a1 = a.sort {|a, b| a <=> b } @@ -3394,6 +3660,7 @@ rb_ary_sort_bang(VALUE ary) * * When the block returns zero, the order for +a+ and +b+ is indeterminate, * and may be unstable: + * * a = 'abcde'.split('').shuffle * a # => ["e", "b", "d", "a", "c"] * a1 = a.sort {|a, b| 0 } @@ -3428,7 +3695,7 @@ rb_ary_bsearch(VALUE ary) VALUE index_result = rb_ary_bsearch_index(ary); if (FIXNUM_P(index_result)) { - return rb_ary_entry(ary, FIX2LONG(index_result)); + return rb_ary_entry(ary, FIX2LONG(index_result)); } return index_result; } @@ -3451,39 +3718,39 @@ rb_ary_bsearch_index(VALUE ary) RETURN_ENUMERATOR(ary, 0, 0); while (low < high) { - mid = low + ((high - low) / 2); - val = rb_ary_entry(ary, mid); - v = rb_yield(val); - if (FIXNUM_P(v)) { - if (v == INT2FIX(0)) return INT2FIX(mid); - smaller = (SIGNED_VALUE)v < 0; /* Fixnum preserves its sign-bit */ - } - else if (v == Qtrue) { - satisfied = 1; - smaller = 1; - } - else if (!RTEST(v)) { - smaller = 0; - } - else if (rb_obj_is_kind_of(v, rb_cNumeric)) { - const VALUE zero = INT2FIX(0); - switch (rb_cmpint(rb_funcallv(v, id_cmp, 1, &zero), v, zero)) { - case 0: return INT2FIX(mid); - case 1: smaller = 1; break; - case -1: smaller = 0; - } - } - else { - rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE - " (must be numeric, true, false or nil)", - rb_obj_class(v)); - } - if (smaller) { - high = mid; - } - else { - low = mid + 1; - } + mid = low + ((high - low) / 2); + val = rb_ary_entry(ary, mid); + v = rb_yield(val); + if (FIXNUM_P(v)) { + if (v == INT2FIX(0)) return INT2FIX(mid); + smaller = (SIGNED_VALUE)v < 0; /* Fixnum preserves its sign-bit */ + } + else if (v == Qtrue) { + satisfied = 1; + smaller = 1; + } + else if (!RTEST(v)) { + smaller = 0; + } + else if (rb_obj_is_kind_of(v, rb_cNumeric)) { + const VALUE zero = INT2FIX(0); + switch (rb_cmpint(rb_funcallv(v, id_cmp, 1, &zero), v, zero)) { + case 0: return INT2FIX(mid); + case 1: smaller = 0; break; + case -1: smaller = 1; + } + } + else { + rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE + " (must be numeric, true, false or nil)", + rb_obj_class(v)); + } + if (smaller) { + high = mid; + } + else { + low = mid + 1; + } } if (!satisfied) return Qnil; return INT2FIX(low); @@ -3510,6 +3777,7 @@ sort_by_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, dummy)) * For duplicates returned by the block, the ordering is indeterminate, and may be unstable. * * This example sorts strings based on their sizes: + * * a = ['aaaa', 'bbb', 'cc', 'd'] * a.sort_by! {|element| element.size } * a # => ["d", "cc", "bbb", "aaaa"] @@ -3518,6 +3786,7 @@ sort_by_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, dummy)) * * a = ['aaaa', 'bbb', 'cc', 'd'] * a.sort_by! # => #<Enumerator: ["aaaa", "bbb", "cc", "d"]:sort_by!> + * */ static VALUE @@ -3540,6 +3809,7 @@ rb_ary_sort_by_bang(VALUE ary) * * Calls the block, if given, with each element of +self+; * returns a new \Array whose elements are the return values from the block: + * * a = [:foo, 'bar', 2] * a1 = a.map {|element| element.class } * a1 # => [Symbol, String, Integer] @@ -3574,10 +3844,12 @@ rb_ary_collect(VALUE ary) * * Calls the block, if given, with each element; * replaces the element with the block's return value: + * * a = [:foo, 'bar', 2] * a.map! { |element| element.class } # => [Symbol, String, Integer] * * Returns a new \Enumerator if no block given: + * * a = [:foo, 'bar', 2] * a1 = a.map! * a1 # => #<Enumerator: [:foo, "bar", 2]:map!> @@ -3593,7 +3865,7 @@ rb_ary_collect_bang(VALUE ary) RETURN_SIZED_ENUMERATOR(ary, 0, 0, ary_enum_length); rb_ary_modify(ary); for (i = 0; i < RARRAY_LEN(ary); i++) { - rb_ary_store(ary, i, rb_yield(RARRAY_AREF(ary, i))); + rb_ary_store(ary, i, rb_yield(RARRAY_AREF(ary, i))); } return ary; } @@ -3605,21 +3877,21 @@ rb_get_values_at(VALUE obj, long olen, int argc, const VALUE *argv, VALUE (*func long beg, len, i, j; for (i=0; i<argc; i++) { - if (FIXNUM_P(argv[i])) { - rb_ary_push(result, (*func)(obj, FIX2LONG(argv[i]))); - continue; - } - /* check if idx is Range */ - if (rb_range_beg_len(argv[i], &beg, &len, olen, 1)) { - long end = olen < beg+len ? olen : beg+len; - for (j = beg; j < end; j++) { - rb_ary_push(result, (*func)(obj, j)); - } - if (beg + len > j) - rb_ary_resize(result, RARRAY_LEN(result) + (beg + len) - j); - continue; - } - rb_ary_push(result, (*func)(obj, NUM2LONG(argv[i]))); + if (FIXNUM_P(argv[i])) { + rb_ary_push(result, (*func)(obj, FIX2LONG(argv[i]))); + continue; + } + /* check if idx is Range */ + if (rb_range_beg_len(argv[i], &beg, &len, olen, 1)) { + long end = olen < beg+len ? olen : beg+len; + for (j = beg; j < end; j++) { + rb_ary_push(result, (*func)(obj, j)); + } + if (beg + len > j) + rb_ary_resize(result, RARRAY_LEN(result) + (beg + len) - j); + continue; + } + rb_ary_push(result, (*func)(obj, NUM2LONG(argv[i]))); } return result; } @@ -3629,25 +3901,25 @@ append_values_at_single(VALUE result, VALUE ary, long olen, VALUE idx) { long beg, len; if (FIXNUM_P(idx)) { - beg = FIX2LONG(idx); + beg = FIX2LONG(idx); } /* check if idx is Range */ else if (rb_range_beg_len(idx, &beg, &len, olen, 1)) { - if (len > 0) { + if (len > 0) { const VALUE *const src = RARRAY_CONST_PTR_TRANSIENT(ary); - const long end = beg + len; - const long prevlen = RARRAY_LEN(result); - if (beg < olen) { - rb_ary_cat(result, src + beg, end > olen ? olen-beg : len); - } - if (end > olen) { - rb_ary_store(result, prevlen + len - 1, Qnil); - } - } - return result; + const long end = beg + len; + const long prevlen = RARRAY_LEN(result); + if (beg < olen) { + rb_ary_cat(result, src + beg, end > olen ? olen-beg : len); + } + if (end > olen) { + rb_ary_store(result, prevlen + len - 1, Qnil); + } + } + return result; } else { - beg = NUM2LONG(idx); + beg = NUM2LONG(idx); } return rb_ary_push(result, rb_ary_entry(ary, beg)); } @@ -3660,32 +3932,39 @@ append_values_at_single(VALUE result, VALUE ary, long olen, VALUE idx) * of +self+ at the given \Integer or \Range +indexes+. * * For each positive +index+, returns the element at offset +index+: + * * a = [:foo, 'bar', 2] * a.values_at(0, 2) # => [:foo, 2] * a.values_at(0..1) # => [:foo, "bar"] * * The given +indexes+ may be in any order, and may repeat: + * * a = [:foo, 'bar', 2] * a.values_at(2, 0, 1, 0, 2) # => [2, :foo, "bar", :foo, 2] * a.values_at(1, 0..2) # => ["bar", :foo, "bar", 2] * * Assigns +nil+ for an +index+ that is too large: + * * a = [:foo, 'bar', 2] * a.values_at(0, 3, 1, 3) # => [:foo, nil, "bar", nil] * * Returns a new empty \Array if no arguments given. * * For each negative +index+, counts backward from the end of the array: + * * a = [:foo, 'bar', 2] * a.values_at(-1, -3) # => [2, :foo] * * Assigns +nil+ for an +index+ that is too small: + * * a = [:foo, 'bar', 2] * a.values_at(0, -5, 1, -6, 2) # => [:foo, nil, "bar", nil, 2] * * The given +indexes+ may have a mixture of signs: + * * a = [:foo, 'bar', 2] * a.values_at(0, -2, 1, -1) # => [:foo, "bar", "bar", 2] + * */ static VALUE @@ -3694,7 +3973,7 @@ rb_ary_values_at(int argc, VALUE *argv, VALUE ary) long i, olen = RARRAY_LEN(ary); VALUE result = rb_ary_new_capa(argc); for (i = 0; i < argc; ++i) { - append_values_at_single(result, ary, olen, argv[i]); + append_values_at_single(result, ary, olen, argv[i]); } RB_GC_GUARD(ary); return result; @@ -3709,11 +3988,13 @@ rb_ary_values_at(int argc, VALUE *argv, VALUE ary) * Calls the block, if given, with each element of +self+; * returns a new \Array containing those elements of +self+ * for which the block returns a truthy value: + * * a = [:foo, 'bar', 2, :bam] * a1 = a.select {|element| element.to_s.start_with?('b') } * a1 # => ["bar", :bam] * * Returns a new \Enumerator if no block given: + * * a = [:foo, 'bar', 2, :bam] * a.select # => #<Enumerator: [:foo, "bar", 2, :bam]:select> * @@ -3729,9 +4010,9 @@ rb_ary_select(VALUE ary) RETURN_SIZED_ENUMERATOR(ary, 0, 0, ary_enum_length); result = rb_ary_new2(RARRAY_LEN(ary)); for (i = 0; i < RARRAY_LEN(ary); i++) { - if (RTEST(rb_yield(RARRAY_AREF(ary, i)))) { - rb_ary_push(result, rb_ary_elt(ary, i)); - } + if (RTEST(rb_yield(RARRAY_AREF(ary, i)))) { + rb_ary_push(result, rb_ary_elt(ary, i)); + } } return result; } @@ -3749,12 +4030,12 @@ select_bang_i(VALUE a) long i1, i2; for (i1 = i2 = 0; i1 < RARRAY_LEN(ary); arg->len[0] = ++i1) { - VALUE v = RARRAY_AREF(ary, i1); - if (!RTEST(rb_yield(v))) continue; - if (i1 != i2) { - rb_ary_store(ary, i2, v); - } - arg->len[1] = ++i2; + VALUE v = RARRAY_AREF(ary, i1); + if (!RTEST(rb_yield(v))) continue; + if (i1 != i2) { + rb_ary_store(ary, i2, v); + } + arg->len[1] = ++i2; } return (i1 == i2) ? Qnil : ary; } @@ -3768,15 +4049,15 @@ select_bang_ensure(VALUE a) long i1 = arg->len[0], i2 = arg->len[1]; if (i2 < len && i2 < i1) { - long tail = 0; + long tail = 0; rb_ary_modify(ary); - if (i1 < len) { - tail = len - i1; + if (i1 < len) { + tail = len - i1; RARRAY_PTR_USE_TRANSIENT(ary, ptr, { - MEMMOVE(ptr + i2, ptr + i1, VALUE, tail); - }); - } - ARY_SET_LEN(ary, i2 + tail); + MEMMOVE(ptr + i2, ptr + i1, VALUE, tail); + }); + } + ARY_SET_LEN(ary, i2 + tail); } return ary; } @@ -3790,12 +4071,14 @@ select_bang_ensure(VALUE a) * removes from +self+ those elements for which the block returns +false+ or +nil+. * * Returns +self+ if any elements were removed: + * * a = [:foo, 'bar', 2, :bam] * a.select! {|element| element.to_s.start_with?('b') } # => ["bar", :bam] * * Returns +nil+ if no elements were removed. * * Returns a new \Enumerator if no block given: + * * a = [:foo, 'bar', 2, :bam] * a.select! # => #<Enumerator: [:foo, "bar", 2, :bam]:select!> * @@ -3822,12 +4105,15 @@ rb_ary_select_bang(VALUE ary) * * Retains those elements for which the block returns a truthy value; * deletes all other elements; returns +self+: + * * a = [:foo, 'bar', 2, :bam] * a.keep_if {|element| element.to_s.start_with?('b') } # => ["bar", :bam] * * Returns a new \Enumerator if no block given: + * * a = [:foo, 'bar', 2, :bam] * a.keep_if # => #<Enumerator: [:foo, "bar", 2, :bam]:keep_if> + * */ static VALUE @@ -3843,11 +4129,11 @@ ary_resize_smaller(VALUE ary, long len) { rb_ary_modify(ary); if (RARRAY_LEN(ary) > len) { - ARY_SET_LEN(ary, len); - if (len * 2 < ARY_CAPA(ary) && - ARY_CAPA(ary) > ARY_DEFAULT_SIZE) { - ary_resize_capa(ary, len * 2); - } + ARY_SET_LEN(ary, len); + if (len * 2 < ARY_CAPA(ary) && + ARY_CAPA(ary) > ARY_DEFAULT_SIZE) { + ary_resize_capa(ary, len * 2); + } } } @@ -3856,11 +4142,12 @@ ary_resize_smaller(VALUE ary, long len) * array.delete(obj) -> deleted_object * array.delete(obj) {|nosuch| ... } -> deleted_object or block_return * - * Removes zero or more elements from +self+; returns +self+. + * Removes zero or more elements from +self+. * * When no block is given, * removes from +self+ each element +ele+ such that <tt>ele == obj</tt>; * returns the last deleted element: + * * s1 = 'bar'; s2 = 'bar' * a = [:foo, s1, 2, s2] * a.delete('bar') # => "bar" @@ -3873,14 +4160,17 @@ ary_resize_smaller(VALUE ary, long len) * * If any such elements are found, ignores the block * and returns the last deleted element: + * * s1 = 'bar'; s2 = 'bar' * a = [:foo, s1, 2, s2] * deleted_obj = a.delete('bar') {|obj| fail 'Cannot happen' } * a # => [:foo, 2] * * If no such elements are found, returns the block's return value: + * * a = [:foo, 'bar', 2] * a.delete(:nosuch) {|obj| "#{obj} not found" } # => "nosuch not found" + * */ VALUE @@ -3890,22 +4180,22 @@ rb_ary_delete(VALUE ary, VALUE item) long i1, i2; for (i1 = i2 = 0; i1 < RARRAY_LEN(ary); i1++) { - VALUE e = RARRAY_AREF(ary, i1); + VALUE e = RARRAY_AREF(ary, i1); - if (rb_equal(e, item)) { - v = e; - continue; - } - if (i1 != i2) { - rb_ary_store(ary, i2, e); - } - i2++; + if (rb_equal(e, item)) { + v = e; + continue; + } + if (i1 != i2) { + rb_ary_store(ary, i2, e); + } + i2++; } if (RARRAY_LEN(ary) == i2) { - if (rb_block_given_p()) { - return rb_yield(item); - } - return Qnil; + if (rb_block_given_p()) { + return rb_yield(item); + } + return Qnil; } ary_resize_smaller(ary, i2); @@ -3920,18 +4210,18 @@ rb_ary_delete_same(VALUE ary, VALUE item) long i1, i2; for (i1 = i2 = 0; i1 < RARRAY_LEN(ary); i1++) { - VALUE e = RARRAY_AREF(ary, i1); + VALUE e = RARRAY_AREF(ary, i1); - if (e == item) { - continue; - } - if (i1 != i2) { - rb_ary_store(ary, i2, e); - } - i2++; + if (e == item) { + continue; + } + if (i1 != i2) { + rb_ary_store(ary, i2, e); + } + i2++; } if (RARRAY_LEN(ary) == i2) { - return; + return; } ary_resize_smaller(ary, i2); @@ -3945,8 +4235,8 @@ rb_ary_delete_at(VALUE ary, long pos) if (pos >= len) return Qnil; if (pos < 0) { - pos += len; - if (pos < 0) return Qnil; + pos += len; + if (pos < 0) return Qnil; } rb_ary_modify(ary); @@ -3966,6 +4256,7 @@ rb_ary_delete_at(VALUE ary, long pos) * Deletes an element from +self+, per the given \Integer +index+. * * When +index+ is non-negative, deletes the element at offset +index+: + * * a = [:foo, 'bar', 2] * a.delete_at(1) # => "bar" * a # => [:foo, 2] @@ -3973,6 +4264,7 @@ rb_ary_delete_at(VALUE ary, long pos) * If index is too large, returns +nil+. * * When +index+ is negative, counts backward from the end of the array: + * * a = [:foo, 'bar', 2] * a.delete_at(-2) # => "bar" * a # => [:foo, 2] @@ -4026,11 +4318,13 @@ ary_slice_bang_by_rb_ary_splice(VALUE ary, long pos, long len) * * When the only argument is an \Integer +n+, * removes and returns the _nth_ element in +self+: + * * a = [:foo, 'bar', 2] * a.slice!(1) # => "bar" * a # => [:foo, 2] * * If +n+ is negative, counts backwards from the end of +self+: + * * a = [:foo, 'bar', 2] * a.slice!(-1) # => 2 * a # => [:foo, "bar"] @@ -4039,13 +4333,15 @@ ary_slice_bang_by_rb_ary_splice(VALUE ary, long pos, long len) * * When the only arguments are Integers +start+ and +length+, * removes +length+ elements from +self+ beginning at offset +start+; - * returns the deleted objects in a new Array: + * returns the deleted objects in a new \Array: + * * a = [:foo, 'bar', 2] * a.slice!(0, 2) # => [:foo, "bar"] * a # => [2] * * If <tt>start + length</tt> exceeds the array size, * removes and returns all elements from offset +start+ to the end: + * * a = [:foo, 'bar', 2] * a.slice!(1, 50) # => ["bar", 2] * a # => [:foo] @@ -4057,8 +4353,9 @@ ary_slice_bang_by_rb_ary_splice(VALUE ary, long pos, long len) * * When the only argument is a \Range object +range+, * treats <tt>range.min</tt> as +start+ above and <tt>range.size</tt> as +length+ above: + * * a = [:foo, 'bar', 2] - * a.slice!(1..2) # => ["bar", 2] + * a.slice!(1..2) # => ["bar", 2] * a # => [:foo] * * If <tt>range.start == a.size</tt>, returns a new empty \Array. @@ -4066,15 +4363,18 @@ ary_slice_bang_by_rb_ary_splice(VALUE ary, long pos, long len) * If <tt>range.start</tt> is larger than the array size, returns +nil+. * * If <tt>range.end</tt> is negative, counts backwards from the end of the array: + * * a = [:foo, 'bar', 2] * a.slice!(0..-2) # => [:foo, "bar"] * a # => [2] * * If <tt>range.start</tt> is negative, * calculates the start index backwards from the end of the array: + * * a = [:foo, 'bar', 2] * a.slice!(-2..2) # => ["bar", 2] * a # => [:foo] + * */ static VALUE @@ -4088,23 +4388,23 @@ rb_ary_slice_bang(int argc, VALUE *argv, VALUE ary) arg1 = argv[0]; if (argc == 2) { - pos = NUM2LONG(argv[0]); - len = NUM2LONG(argv[1]); + pos = NUM2LONG(argv[0]); + len = NUM2LONG(argv[1]); return ary_slice_bang_by_rb_ary_splice(ary, pos, len); } if (!FIXNUM_P(arg1)) { - switch (rb_range_beg_len(arg1, &pos, &len, RARRAY_LEN(ary), 0)) { - case Qtrue: - /* valid range */ + switch (rb_range_beg_len(arg1, &pos, &len, RARRAY_LEN(ary), 0)) { + case Qtrue: + /* valid range */ return ary_slice_bang_by_rb_ary_splice(ary, pos, len); - case Qnil: - /* invalid range */ - return Qnil; - default: - /* not a range */ - break; - } + case Qnil: + /* invalid range */ + return Qnil; + default: + /* not a range */ + break; + } } return rb_ary_delete_at(ary, NUM2LONG(arg1)); @@ -4116,11 +4416,11 @@ ary_reject(VALUE orig, VALUE result) long i; for (i = 0; i < RARRAY_LEN(orig); i++) { - VALUE v = RARRAY_AREF(orig, i); + VALUE v = RARRAY_AREF(orig, i); if (!RTEST(rb_yield(v))) { - rb_ary_push(result, v); - } + rb_ary_push(result, v); + } } return result; } @@ -4133,12 +4433,12 @@ reject_bang_i(VALUE a) long i1, i2; for (i1 = i2 = 0; i1 < RARRAY_LEN(ary); arg->len[0] = ++i1) { - VALUE v = RARRAY_AREF(ary, i1); - if (RTEST(rb_yield(v))) continue; - if (i1 != i2) { - rb_ary_store(ary, i2, v); - } - arg->len[1] = ++i2; + VALUE v = RARRAY_AREF(ary, i1); + if (RTEST(rb_yield(v))) continue; + if (i1 != i2) { + rb_ary_store(ary, i2, v); + } + arg->len[1] = ++i2; } return (i1 == i2) ? Qnil : ary; } @@ -4161,14 +4461,17 @@ ary_reject_bang(VALUE ary) * Removes each element for which the block returns a truthy value. * * Returns +self+ if any elements removed: + * * a = [:foo, 'bar', 2, 'bat'] * a.reject! {|element| element.to_s.start_with?('b') } # => [:foo, 2] * * Returns +nil+ if no elements removed. * * Returns a new \Enumerator if no block given: + * * a = [:foo, 'bar', 2] * a.reject! # => #<Enumerator: [:foo, "bar", 2]:reject!> + * */ static VALUE @@ -4186,13 +4489,16 @@ rb_ary_reject_bang(VALUE ary) * * Returns a new \Array whose elements are all those from +self+ * for which the block returns +false+ or +nil+: + * * a = [:foo, 'bar', 2, 'bat'] * a1 = a.reject {|element| element.to_s.start_with?('b') } * a1 # => [:foo, 2] * * Returns a new \Enumerator if no block given: + * * a = [:foo, 'bar', 2] * a.reject # => #<Enumerator: [:foo, "bar", 2]:reject> + * */ static VALUE @@ -4213,13 +4519,16 @@ rb_ary_reject(VALUE ary) * * Removes each element in +self+ for which the block returns a truthy value; * returns +self+: + * * a = [:foo, 'bar', 2, 'bat'] * a.delete_if {|element| element.to_s.start_with?('b') } # => [:foo, 2] * * Returns a new \Enumerator if no block given: + * * a = [:foo, 'bar', 2] * a.delete_if # => #<Enumerator: [:foo, "bar", 2]:delete_if> - */ + * +3 */ static VALUE rb_ary_delete_if(VALUE ary) @@ -4250,9 +4559,9 @@ take_items(VALUE obj, long n) if (!NIL_P(result)) return rb_ary_subseq(result, 0, n); result = rb_ary_new2(n); args[0] = result; args[1] = (VALUE)n; - if (rb_check_block_call(obj, idEach, 0, 0, take_i, (VALUE)args) == Qundef) - rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (must respond to :each)", - rb_obj_class(obj)); + if (UNDEF_P(rb_check_block_call(obj, idEach, 0, 0, take_i, (VALUE)args))) + rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (must respond to :each)", + rb_obj_class(obj)); return result; } @@ -4267,10 +4576,12 @@ take_items(VALUE obj, long n) * * Each nested array <tt>new_array[n]</tt> is of size <tt>other_arrays.size+1</tt>, * and contains: + * * - The _nth_ element of +self+. * - The _nth_ element of each of the +other_arrays+. * * If all +other_arrays+ and +self+ are the same size: + * * a = [:a0, :a1, :a2, :a3] * b = [:b0, :b1, :b2, :b3] * c = [:c0, :c1, :c2, :c3] @@ -4279,6 +4590,7 @@ take_items(VALUE obj, long n) * * If any array in +other_arrays+ is smaller than +self+, * fills to <tt>self.size</tt> with +nil+: + * * a = [:a0, :a1, :a2, :a3] * b = [:b0, :b1, :b2] * c = [:c0, :c1] @@ -4287,23 +4599,27 @@ take_items(VALUE obj, long n) * * If any array in +other_arrays+ is larger than +self+, * its trailing elements are ignored: + * * a = [:a0, :a1, :a2, :a3] * b = [:b0, :b1, :b2, :b3, :b4] * c = [:c0, :c1, :c2, :c3, :c4, :c5] * d = a.zip(b, c) * d # => [[:a0, :b0, :c0], [:a1, :b1, :c1], [:a2, :b2, :c2], [:a3, :b3, :c3]] * - * When a block is given, calls the block with each of the sub-arrays (formed as above); returns nil + * When a block is given, calls the block with each of the sub-arrays (formed as above); returns +nil+: + * * a = [:a0, :a1, :a2, :a3] * b = [:b0, :b1, :b2, :b3] * c = [:c0, :c1, :c2, :c3] * a.zip(b, c) {|sub_array| p sub_array} # => nil * * Output: + * * [:a0, :b0, :c0] * [:a1, :b1, :c1] * [:a2, :b2, :c2] * [:a3, :b3, :c3] + * */ static VALUE @@ -4314,51 +4630,51 @@ rb_ary_zip(int argc, VALUE *argv, VALUE ary) VALUE result = Qnil; for (i=0; i<argc; i++) { - argv[i] = take_items(argv[i], len); + argv[i] = take_items(argv[i], len); } if (rb_block_given_p()) { - int arity = rb_block_arity(); - - if (arity > 1) { - VALUE work, *tmp; - - tmp = ALLOCV_N(VALUE, work, argc+1); - - for (i=0; i<RARRAY_LEN(ary); i++) { - tmp[0] = RARRAY_AREF(ary, i); - for (j=0; j<argc; j++) { - tmp[j+1] = rb_ary_elt(argv[j], i); - } - rb_yield_values2(argc+1, tmp); - } - - if (work) ALLOCV_END(work); - } - else { - for (i=0; i<RARRAY_LEN(ary); i++) { - VALUE tmp = rb_ary_new2(argc+1); - - rb_ary_push(tmp, RARRAY_AREF(ary, i)); - for (j=0; j<argc; j++) { - rb_ary_push(tmp, rb_ary_elt(argv[j], i)); - } - rb_yield(tmp); - } - } + int arity = rb_block_arity(); + + if (arity > 1) { + VALUE work, *tmp; + + tmp = ALLOCV_N(VALUE, work, argc+1); + + for (i=0; i<RARRAY_LEN(ary); i++) { + tmp[0] = RARRAY_AREF(ary, i); + for (j=0; j<argc; j++) { + tmp[j+1] = rb_ary_elt(argv[j], i); + } + rb_yield_values2(argc+1, tmp); + } + + if (work) ALLOCV_END(work); + } + else { + for (i=0; i<RARRAY_LEN(ary); i++) { + VALUE tmp = rb_ary_new2(argc+1); + + rb_ary_push(tmp, RARRAY_AREF(ary, i)); + for (j=0; j<argc; j++) { + rb_ary_push(tmp, rb_ary_elt(argv[j], i)); + } + rb_yield(tmp); + } + } } else { - result = rb_ary_new_capa(len); + result = rb_ary_new_capa(len); - for (i=0; i<len; i++) { - VALUE tmp = rb_ary_new_capa(argc+1); + for (i=0; i<len; i++) { + VALUE tmp = rb_ary_new_capa(argc+1); - rb_ary_push(tmp, RARRAY_AREF(ary, i)); - for (j=0; j<argc; j++) { - rb_ary_push(tmp, rb_ary_elt(argv[j], i)); - } - rb_ary_push(result, tmp); - } + rb_ary_push(tmp, RARRAY_AREF(ary, i)); + for (j=0; j<argc; j++) { + rb_ary_push(tmp, rb_ary_elt(argv[j], i)); + } + rb_ary_push(result, tmp); + } } return result; @@ -4370,8 +4686,10 @@ rb_ary_zip(int argc, VALUE *argv, VALUE ary) * * Transposes the rows and columns in an \Array of Arrays; * the nested Arrays must all be the same size: + * * a = [[:a0, :a1], [:b0, :b1], [:c0, :c1]] * a.transpose # => [[:a0, :b0, :c0], [:a1, :b1, :c1]] + * */ static VALUE @@ -4383,21 +4701,21 @@ rb_ary_transpose(VALUE ary) alen = RARRAY_LEN(ary); if (alen == 0) return rb_ary_dup(ary); for (i=0; i<alen; i++) { - tmp = to_ary(rb_ary_elt(ary, i)); - if (elen < 0) { /* first element */ - elen = RARRAY_LEN(tmp); - result = rb_ary_new2(elen); - for (j=0; j<elen; j++) { - rb_ary_store(result, j, rb_ary_new2(alen)); - } - } - else if (elen != RARRAY_LEN(tmp)) { - rb_raise(rb_eIndexError, "element size differs (%ld should be %ld)", - RARRAY_LEN(tmp), elen); - } - for (j=0; j<elen; j++) { - rb_ary_store(rb_ary_elt(result, j), i, rb_ary_elt(tmp, j)); - } + tmp = to_ary(rb_ary_elt(ary, i)); + if (elen < 0) { /* first element */ + elen = RARRAY_LEN(tmp); + result = rb_ary_new2(elen); + for (j=0; j<elen; j++) { + rb_ary_store(result, j, rb_ary_new2(alen)); + } + } + else if (elen != RARRAY_LEN(tmp)) { + rb_raise(rb_eIndexError, "element size differs (%ld should be %ld)", + RARRAY_LEN(tmp), elen); + } + for (j=0; j<elen; j++) { + rb_ary_store(rb_ary_elt(result, j), i, rb_ary_elt(tmp, j)); + } } return result; } @@ -4407,8 +4725,10 @@ rb_ary_transpose(VALUE ary) * array.replace(other_array) -> self * * Replaces the content of +self+ with the content of +other_array+; returns +self+: + * * a = [:foo, 'bar', 2] * a.replace(['foo', :bar, 3]) # => ["foo", :bar, 3] + * */ VALUE @@ -4418,31 +4738,35 @@ rb_ary_replace(VALUE copy, VALUE orig) orig = to_ary(orig); if (copy == orig) return copy; - if (RARRAY_LEN(orig) <= RARRAY_EMBED_LEN_MAX) { - VALUE shared_root = 0; + rb_ary_reset(copy); - if (ARY_OWNS_HEAP_P(copy)) { - ary_heap_free(copy); - } - else if (ARY_SHARED_P(copy)) { - shared_root = ARY_SHARED_ROOT(copy); - FL_UNSET_SHARED(copy); - } - FL_SET_EMBED(copy); + /* orig has enough space to embed the contents of orig. */ + if (RARRAY_LEN(orig) <= ary_embed_capa(copy)) { + assert(ARY_EMBED_P(copy)); ary_memcpy(copy, 0, RARRAY_LEN(orig), RARRAY_CONST_PTR_TRANSIENT(orig)); - if (shared_root) { - rb_ary_decrement_share(shared_root); - } - ARY_SET_LEN(copy, RARRAY_LEN(orig)); + ARY_SET_EMBED_LEN(copy, RARRAY_LEN(orig)); } +#if USE_RVARGC + /* orig is embedded but copy does not have enough space to embed the + * contents of orig. */ + else if (ARY_EMBED_P(orig)) { + long len = ARY_EMBED_LEN(orig); + VALUE *ptr = ary_heap_alloc(copy, len); + + FL_UNSET_EMBED(copy); + ARY_SET_PTR(copy, ptr); + ARY_SET_LEN(copy, len); + ARY_SET_CAPA(copy, len); + + // No allocation and exception expected that could leave `copy` in a + // bad state from the edits above. + ary_memcpy(copy, 0, len, RARRAY_CONST_PTR_TRANSIENT(orig)); + } +#endif + /* Otherwise, orig is on heap and copy does not have enough space to embed + * the contents of orig. */ else { VALUE shared_root = ary_make_shared(orig); - if (ARY_OWNS_HEAP_P(copy)) { - ary_heap_free(copy); - } - else { - rb_ary_unshare_safe(copy); - } FL_UNSET_EMBED(copy); ARY_SET_PTR(copy, ARY_HEAP_PTR(orig)); ARY_SET_LEN(copy, ARY_HEAP_LEN(orig)); @@ -4457,8 +4781,10 @@ rb_ary_replace(VALUE copy, VALUE orig) * array.clear -> self * * Removes all elements from +self+: + * * a = [:foo, 'bar', 2] * a.clear # => [] + * */ VALUE @@ -4466,11 +4792,11 @@ rb_ary_clear(VALUE ary) { rb_ary_modify_check(ary); if (ARY_SHARED_P(ary)) { - if (!ARY_EMBED_P(ary)) { - rb_ary_unshare(ary); - FL_SET_EMBED(ary); + if (!ARY_EMBED_P(ary)) { + rb_ary_unshare(ary); + FL_SET_EMBED(ary); ARY_SET_EMBED_LEN(ary, 0); - } + } } else { ARY_SET_LEN(ary, 0); @@ -4496,6 +4822,7 @@ rb_ary_clear(VALUE ary) * Replaces specified elements in +self+ with specified objects; returns +self+. * * With argument +obj+ and no block given, replaces all elements with that one object: + * * a = ['a', 'b', 'c', 'd'] * a # => ["a", "b", "c", "d"] * a.fill(:X) # => [:X, :X, :X, :X] @@ -4505,20 +4832,24 @@ rb_ary_clear(VALUE ary) * * If +start+ is in range (<tt>0 <= start < array.size</tt>), * replaces all elements from offset +start+ through the end: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, 2) # => ["a", "b", :X, :X] * * If +start+ is too large (<tt>start >= array.size</tt>), does nothing: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, 4) # => ["a", "b", "c", "d"] * a = ['a', 'b', 'c', 'd'] * a.fill(:X, 5) # => ["a", "b", "c", "d"] * * If +start+ is negative, counts from the end (starting index is <tt>start + array.size</tt>): + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, -2) # => ["a", "b", :X, :X] * * If +start+ is too small (less than and far from zero), replaces all elements: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, -6) # => [:X, :X, :X, :X] * a = ['a', 'b', 'c', 'd'] @@ -4528,20 +4859,24 @@ rb_ary_clear(VALUE ary) * replaces elements based on the given +start+ and +length+. * * If +start+ is in range, replaces +length+ elements beginning at offset +start+: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, 1, 1) # => ["a", :X, "c", "d"] * * If +start+ is negative, counts from the end: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, -2, 1) # => ["a", "b", :X, "d"] * * If +start+ is large (<tt>start >= array.size</tt>), extends +self+ with +nil+: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, 5, 0) # => ["a", "b", "c", "d", nil] * a = ['a', 'b', 'c', 'd'] * a.fill(:X, 5, 2) # => ["a", "b", "c", "d", nil, :X, :X] * * If +length+ is zero or negative, replaces no elements: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, 1, 0) # => ["a", "b", "c", "d"] * a.fill(:X, 1, -1) # => ["a", "b", "c", "d"] @@ -4551,14 +4886,17 @@ rb_ary_clear(VALUE ary) * * If the range is positive and ascending (<tt>0 < range.begin <= range.end</tt>), * replaces elements from <tt>range.begin</tt> to <tt>range.end</tt>: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, (1..1)) # => ["a", :X, "c", "d"] * * If <tt>range.first</tt> is negative, replaces no elements: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, (-1..1)) # => ["a", "b", "c", "d"] * * If <tt>range.last</tt> is negative, counts from the end: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, (0..-2)) # => [:X, :X, :X, "d"] * a = ['a', 'b', 'c', 'd'] @@ -4566,6 +4904,7 @@ rb_ary_clear(VALUE ary) * * If <tt>range.last</tt> and <tt>range.last</tt> are both negative, * both count from the end of the array: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, (-1..-1)) # => ["a", "b", "c", :X] * a = ['a', 'b', 'c', 'd'] @@ -4573,29 +4912,34 @@ rb_ary_clear(VALUE ary) * * With no arguments and a block given, calls the block with each index; * replaces the corresponding element with the block's return value: + * * a = ['a', 'b', 'c', 'd'] * a.fill { |index| "new_#{index}" } # => ["new_0", "new_1", "new_2", "new_3"] * * With argument +start+ and a block given, calls the block with each index * from offset +start+ to the end; replaces the corresponding element - * with the block's return value: + * with the block's return value. * * If start is in range (<tt>0 <= start < array.size</tt>), * replaces from offset +start+ to the end: + * * a = ['a', 'b', 'c', 'd'] * a.fill(1) { |index| "new_#{index}" } # => ["a", "new_1", "new_2", "new_3"] * * If +start+ is too large(<tt>start >= array.size</tt>), does nothing: + * * a = ['a', 'b', 'c', 'd'] * a.fill(4) { |index| fail 'Cannot happen' } # => ["a", "b", "c", "d"] * a = ['a', 'b', 'c', 'd'] * a.fill(4) { |index| fail 'Cannot happen' } # => ["a", "b", "c", "d"] * * If +start+ is negative, counts from the end: + * * a = ['a', 'b', 'c', 'd'] * a.fill(-2) { |index| "new_#{index}" } # => ["a", "b", "new_2", "new_3"] * * If start is too small (<tt>start <= -array.size</tt>, replaces all elements: + * * a = ['a', 'b', 'c', 'd'] * a.fill(-6) { |index| "new_#{index}" } # => ["new_0", "new_1", "new_2", "new_3"] * a = ['a', 'b', 'c', 'd'] @@ -4606,20 +4950,24 @@ rb_ary_clear(VALUE ary) * replaces the corresponding element with the block's return value. * * If +start+ is in range, replaces +length+ elements beginning at offset +start+: + * * a = ['a', 'b', 'c', 'd'] * a.fill(1, 1) { |index| "new_#{index}" } # => ["a", "new_1", "c", "d"] * * If start is negative, counts from the end: + * * a = ['a', 'b', 'c', 'd'] * a.fill(-2, 1) { |index| "new_#{index}" } # => ["a", "b", "new_2", "d"] * * If +start+ is large (<tt>start >= array.size</tt>), extends +self+ with +nil+: + * * a = ['a', 'b', 'c', 'd'] * a.fill(5, 0) { |index| "new_#{index}" } # => ["a", "b", "c", "d", nil] * a = ['a', 'b', 'c', 'd'] * a.fill(5, 2) { |index| "new_#{index}" } # => ["a", "b", "c", "d", nil, "new_5", "new_6"] * * If +length+ is zero or less, replaces no elements: + * * a = ['a', 'b', 'c', 'd'] * a.fill(1, 0) { |index| "new_#{index}" } # => ["a", "b", "c", "d"] * a.fill(1, -1) { |index| "new_#{index}" } # => ["a", "b", "c", "d"] @@ -4630,14 +4978,17 @@ rb_ary_clear(VALUE ary) * * If the range is positive and ascending (<tt>range 0 < range.begin <= range.end</tt>, * replaces elements from <tt>range.begin</tt> to <tt>range.end</tt>: + * * a = ['a', 'b', 'c', 'd'] * a.fill(1..1) { |index| "new_#{index}" } # => ["a", "new_1", "c", "d"] * * If +range.first+ is negative, does nothing: + * * a = ['a', 'b', 'c', 'd'] * a.fill(-1..1) { |index| fail 'Cannot happen' } # => ["a", "b", "c", "d"] * * If <tt>range.last</tt> is negative, counts from the end: + * * a = ['a', 'b', 'c', 'd'] * a.fill(0..-2) { |index| "new_#{index}" } # => ["new_0", "new_1", "new_2", "d"] * a = ['a', 'b', 'c', 'd'] @@ -4645,10 +4996,12 @@ rb_ary_clear(VALUE ary) * * If <tt>range.first</tt> and <tt>range.last</tt> are both negative, * both count from the end: + * * a = ['a', 'b', 'c', 'd'] * a.fill(-1..-1) { |index| "new_#{index}" } # => ["a", "b", "c", "new_3"] * a = ['a', 'b', 'c', 'd'] * a.fill(-2..-2) { |index| "new_#{index}" } # => ["a", "b", "new_2", "d"] + * */ static VALUE @@ -4658,59 +5011,59 @@ rb_ary_fill(int argc, VALUE *argv, VALUE ary) long beg = 0, end = 0, len = 0; if (rb_block_given_p()) { - rb_scan_args(argc, argv, "02", &arg1, &arg2); - argc += 1; /* hackish */ + rb_scan_args(argc, argv, "02", &arg1, &arg2); + argc += 1; /* hackish */ } else { - rb_scan_args(argc, argv, "12", &item, &arg1, &arg2); + rb_scan_args(argc, argv, "12", &item, &arg1, &arg2); } switch (argc) { case 1: - beg = 0; - len = RARRAY_LEN(ary); - break; + beg = 0; + len = RARRAY_LEN(ary); + break; case 2: - if (rb_range_beg_len(arg1, &beg, &len, RARRAY_LEN(ary), 1)) { - break; - } - /* fall through */ + if (rb_range_beg_len(arg1, &beg, &len, RARRAY_LEN(ary), 1)) { + break; + } + /* fall through */ case 3: - beg = NIL_P(arg1) ? 0 : NUM2LONG(arg1); - if (beg < 0) { - beg = RARRAY_LEN(ary) + beg; - if (beg < 0) beg = 0; - } - len = NIL_P(arg2) ? RARRAY_LEN(ary) - beg : NUM2LONG(arg2); - break; + beg = NIL_P(arg1) ? 0 : NUM2LONG(arg1); + if (beg < 0) { + beg = RARRAY_LEN(ary) + beg; + if (beg < 0) beg = 0; + } + len = NIL_P(arg2) ? RARRAY_LEN(ary) - beg : NUM2LONG(arg2); + break; } rb_ary_modify(ary); if (len < 0) { return ary; } if (beg >= ARY_MAX_SIZE || len > ARY_MAX_SIZE - beg) { - rb_raise(rb_eArgError, "argument too big"); + rb_raise(rb_eArgError, "argument too big"); } end = beg + len; if (RARRAY_LEN(ary) < end) { - if (end >= ARY_CAPA(ary)) { - ary_resize_capa(ary, end); - } - ary_mem_clear(ary, RARRAY_LEN(ary), end - RARRAY_LEN(ary)); - ARY_SET_LEN(ary, end); + if (end >= ARY_CAPA(ary)) { + ary_resize_capa(ary, end); + } + ary_mem_clear(ary, RARRAY_LEN(ary), end - RARRAY_LEN(ary)); + ARY_SET_LEN(ary, end); } - if (item == Qundef) { - VALUE v; - long i; + if (UNDEF_P(item)) { + VALUE v; + long i; - for (i=beg; i<end; i++) { - v = rb_yield(LONG2NUM(i)); - if (i>=RARRAY_LEN(ary)) break; - ARY_SET(ary, i, v); - } + for (i=beg; i<end; i++) { + v = rb_yield(LONG2NUM(i)); + if (i>=RARRAY_LEN(ary)) break; + ARY_SET(ary, i, v); + } } else { - ary_memfill(ary, beg, len, item); + ary_memfill(ary, beg, len, item); } return ary; } @@ -4721,6 +5074,7 @@ rb_ary_fill(int argc, VALUE *argv, VALUE ary) * * Returns a new \Array containing all elements of +array+ * followed by all elements of +other_array+: + * * a = [0, 1] + [2, 3] * a # => [0, 1, 2, 3] * @@ -4761,6 +5115,7 @@ ary_append(VALUE x, VALUE y) * array.concat(*other_arrays) -> self * * Adds to +array+ all elements from each \Array in +other_arrays+; returns +self+: + * * a = [0, 1] * a.concat([2, 3], [4, 5]) # => [0, 1, 2, 3, 4, 5] */ @@ -4771,15 +5126,15 @@ rb_ary_concat_multi(int argc, VALUE *argv, VALUE ary) rb_ary_modify_check(ary); if (argc == 1) { - rb_ary_concat(ary, argv[0]); + rb_ary_concat(ary, argv[0]); } else if (argc > 1) { - int i; - VALUE args = rb_ary_tmp_new(argc); - for (i = 0; i < argc; i++) { - rb_ary_concat(args, argv[i]); - } - ary_append(ary, args); + int i; + VALUE args = rb_ary_hidden_new(argc); + for (i = 0; i < argc; i++) { + rb_ary_concat(args, argv[i]); + } + ary_append(ary, args); } ary_verify(ary); @@ -4799,12 +5154,15 @@ rb_ary_concat(VALUE x, VALUE y) * * When non-negative argument \Integer +n+ is given, * returns a new \Array built by concatenating the +n+ copies of +self+: + * * a = ['x', 'y'] * a * 3 # => ["x", "y", "x", "y", "x", "y"] * * When \String argument +string_separator+ is given, * equivalent to <tt>array.join(string_separator)</tt>: + * * [0, [0, 1], {foo: 0}] * ', ' # => "0, 0, 1, {:foo=>0}" + * */ static VALUE @@ -4816,19 +5174,19 @@ rb_ary_times(VALUE ary, VALUE times) tmp = rb_check_string_type(times); if (!NIL_P(tmp)) { - return rb_ary_join(ary, tmp); + return rb_ary_join(ary, tmp); } len = NUM2LONG(times); if (len == 0) { ary2 = ary_new(rb_cArray, 0); - goto out; + goto out; } if (len < 0) { - rb_raise(rb_eArgError, "negative argument"); + rb_raise(rb_eArgError, "negative argument"); } if (ARY_MAX_SIZE/len < RARRAY_LEN(ary)) { - rb_raise(rb_eArgError, "argument too big"); + rb_raise(rb_eArgError, "argument too big"); } len *= RARRAY_LEN(ary); @@ -4838,8 +5196,8 @@ rb_ary_times(VALUE ary, VALUE times) ptr = RARRAY_CONST_PTR_TRANSIENT(ary); t = RARRAY_LEN(ary); if (0 < t) { - ary_memcpy(ary2, 0, t, ptr); - while (t <= len/2) { + ary_memcpy(ary2, 0, t, ptr); + while (t <= len/2) { ary_memcpy(ary2, t, t, RARRAY_CONST_PTR_TRANSIENT(ary2)); t *= 2; } @@ -4857,6 +5215,7 @@ rb_ary_times(VALUE ary, VALUE times) * * Returns the first element in +self+ that is an \Array * whose first element <tt>==</tt> +obj+: + * * a = [{foo: 0}, [2, 4], [4, 5, 6], [4, 5]] * a.assoc(4) # => [4, 5, 6] * @@ -4872,10 +5231,10 @@ rb_ary_assoc(VALUE ary, VALUE key) VALUE v; for (i = 0; i < RARRAY_LEN(ary); ++i) { - v = rb_check_array_type(RARRAY_AREF(ary, i)); - if (!NIL_P(v) && RARRAY_LEN(v) > 0 && - rb_equal(RARRAY_AREF(v, 0), key)) - return v; + v = rb_check_array_type(RARRAY_AREF(ary, i)); + if (!NIL_P(v) && RARRAY_LEN(v) > 0 && + rb_equal(RARRAY_AREF(v, 0), key)) + return v; } return Qnil; } @@ -4886,6 +5245,7 @@ rb_ary_assoc(VALUE ary, VALUE key) * * Returns the first element in +self+ that is an \Array * whose second element <tt>==</tt> +obj+: + * * a = [{foo: 0}, [2, 4], [4, 5, 6], [4, 5]] * a.rassoc(4) # => [2, 4] * @@ -4901,11 +5261,11 @@ rb_ary_rassoc(VALUE ary, VALUE value) VALUE v; for (i = 0; i < RARRAY_LEN(ary); ++i) { - v = RARRAY_AREF(ary, i); - if (RB_TYPE_P(v, T_ARRAY) && - RARRAY_LEN(v) > 1 && - rb_equal(RARRAY_AREF(v, 1), value)) - return v; + v = RARRAY_AREF(ary, i); + if (RB_TYPE_P(v, T_ARRAY) && + RARRAY_LEN(v) > 1 && + rb_equal(RARRAY_AREF(v, 1), value)) + return v; } return Qnil; } @@ -4924,22 +5284,22 @@ recursive_equal(VALUE ary1, VALUE ary2, int recur) len1 = RARRAY_LEN(ary1); for (i = 0; i < len1; i++) { - if (*p1 != *p2) { - if (rb_equal(*p1, *p2)) { - len1 = RARRAY_LEN(ary1); - if (len1 != RARRAY_LEN(ary2)) - return Qfalse; - if (len1 < i) - return Qtrue; + if (*p1 != *p2) { + if (rb_equal(*p1, *p2)) { + len1 = RARRAY_LEN(ary1); + if (len1 != RARRAY_LEN(ary2)) + return Qfalse; + if (len1 < i) + return Qtrue; p1 = RARRAY_CONST_PTR(ary1) + i; p2 = RARRAY_CONST_PTR(ary2) + i; - } - else { - return Qfalse; - } - } - p1++; - p2++; + } + else { + return Qfalse; + } + } + p1++; + p2++; } return Qtrue; } @@ -4950,6 +5310,7 @@ recursive_equal(VALUE ary1, VALUE ary2, int recur) * * Returns +true+ if both <tt>array.size == other_array.size</tt> * and for each index +i+ in +array+, <tt>array[i] == other_array[i]</tt>: + * * a0 = [:foo, 'bar', 2] * a1 = [:foo, 'bar', 2.0] * a1 == a0 # => true @@ -4966,10 +5327,10 @@ rb_ary_equal(VALUE ary1, VALUE ary2) { if (ary1 == ary2) return Qtrue; if (!RB_TYPE_P(ary2, T_ARRAY)) { - if (!rb_respond_to(ary2, idTo_ary)) { - return Qfalse; - } - return rb_equal(ary2, ary1); + if (!rb_respond_to(ary2, idTo_ary)) { + return Qfalse; + } + return rb_equal(ary2, ary1); } if (RARRAY_LEN(ary1) != RARRAY_LEN(ary2)) return Qfalse; if (RARRAY_CONST_PTR_TRANSIENT(ary1) == RARRAY_CONST_PTR_TRANSIENT(ary2)) return Qtrue; @@ -4983,8 +5344,8 @@ recursive_eql(VALUE ary1, VALUE ary2, int recur) if (recur) return Qtrue; /* Subtle! */ for (i=0; i<RARRAY_LEN(ary1); i++) { - if (!rb_eql(rb_ary_elt(ary1, i), rb_ary_elt(ary2, i))) - return Qfalse; + if (!rb_eql(rb_ary_elt(ary1, i), rb_ary_elt(ary2, i))) + return Qfalse; } return Qtrue; } @@ -4995,13 +5356,14 @@ recursive_eql(VALUE ary1, VALUE ary2, int recur) * * Returns +true+ if +self+ and +other_array+ are the same size, * and if, for each index +i+ in +self+, <tt>self[i].eql? other_array[i]</tt>: + * * a0 = [:foo, 'bar', 2] * a1 = [:foo, 'bar', 2] * a1.eql?(a0) # => true * * Otherwise, returns +false+. * - * This method is different from method {Array#==}[#method-i-3D-3D], + * This method is different from method Array#==, * which compares using method <tt>Object#==</tt>. */ @@ -5022,8 +5384,10 @@ rb_ary_eql(VALUE ary1, VALUE ary2) * Returns the integer hash value for +self+. * * Two arrays with the same content will have the same hash code (and will compare using eql?): + * * [0, 1, 2].hash == [0, 1, 2].hash # => true * [0, 1, 2].hash == [0, 1, 3].hash # => false + * */ static VALUE @@ -5036,8 +5400,8 @@ rb_ary_hash(VALUE ary) h = rb_hash_start(RARRAY_LEN(ary)); h = rb_hash_uint(h, (st_index_t)rb_ary_hash); for (i=0; i<RARRAY_LEN(ary); i++) { - n = rb_hash(RARRAY_AREF(ary, i)); - h = rb_hash_uint(h, NUM2LONG(n)); + n = rb_hash(RARRAY_AREF(ary, i)); + h = rb_hash_uint(h, NUM2LONG(n)); } h = rb_hash_end(h); return ST2FIX(h); @@ -5049,6 +5413,7 @@ rb_ary_hash(VALUE ary) * * Returns +true+ if for some index +i+ in +self+, <tt>obj == self[i]</tt>; * otherwise +false+: + * * [0, 1, 2].include?(2) # => true * [0, 1, 2].include?(3) # => false */ @@ -5060,10 +5425,10 @@ rb_ary_includes(VALUE ary, VALUE item) VALUE e; for (i=0; i<RARRAY_LEN(ary); i++) { - e = RARRAY_AREF(ary, i); - if (rb_equal(e, item)) { - return Qtrue; - } + e = RARRAY_AREF(ary, i); + if (rb_equal(e, item)) { + return Qtrue; + } } return Qfalse; } @@ -5075,10 +5440,10 @@ rb_ary_includes_by_eql(VALUE ary, VALUE item) VALUE e; for (i=0; i<RARRAY_LEN(ary); i++) { - e = RARRAY_AREF(ary, i); - if (rb_eql(item, e)) { - return Qtrue; - } + e = RARRAY_AREF(ary, i); + if (rb_eql(item, e)) { + return Qtrue; + } } return Qfalse; } @@ -5091,14 +5456,14 @@ recursive_cmp(VALUE ary1, VALUE ary2, int recur) if (recur) return Qundef; /* Subtle! */ len = RARRAY_LEN(ary1); if (len > RARRAY_LEN(ary2)) { - len = RARRAY_LEN(ary2); + len = RARRAY_LEN(ary2); } for (i=0; i<len; i++) { - VALUE e1 = rb_ary_elt(ary1, i), e2 = rb_ary_elt(ary2, i); - VALUE v = rb_funcallv(e1, id_cmp, 1, &e2); - if (v != INT2FIX(0)) { - return v; - } + VALUE e1 = rb_ary_elt(ary1, i), e2 = rb_ary_elt(ary2, i); + VALUE v = rb_funcallv(e1, id_cmp, 1, &e2); + if (v != INT2FIX(0)) { + return v; + } } return Qundef; } @@ -5111,18 +5476,27 @@ recursive_cmp(VALUE ary1, VALUE ary2, int recur) * For each index +i+ in +self+, evaluates <tt>result = self[i] <=> other_array[i]</tt>. * * Returns -1 if any result is -1: + * * [0, 1, 2] <=> [0, 1, 3] # => -1 * * Returns 1 if any result is 1: + * * [0, 1, 2] <=> [0, 1, 1] # => 1 * * When all results are zero: + * * - Returns -1 if +array+ is smaller than +other_array+: + * * [0, 1, 2] <=> [0, 1, 2, 3] # => -1 + * * - Returns 1 if +array+ is larger than +other_array+: + * * [0, 1, 2] <=> [0, 1] # => 1 + * * - Returns 0 if +array+ and +other_array+ are the same size: + * * [0, 1, 2] <=> [0, 1, 2] # => 0 + * */ VALUE @@ -5135,7 +5509,7 @@ rb_ary_cmp(VALUE ary1, VALUE ary2) if (NIL_P(ary2)) return Qnil; if (ary1 == ary2) return INT2FIX(0); v = rb_exec_recursive_paired(recursive_cmp, ary1, ary2, ary2); - if (v != Qundef) return v; + if (!UNDEF_P(v)) return v; len = RARRAY_LEN(ary1) - RARRAY_LEN(ary2); if (len == 0) return INT2FIX(0); if (len > 0) return INT2FIX(1); @@ -5148,8 +5522,8 @@ ary_add_hash(VALUE hash, VALUE ary) long i; for (i=0; i<RARRAY_LEN(ary); i++) { - VALUE elt = RARRAY_AREF(ary, i); - rb_hash_add_new_element(hash, elt, elt); + VALUE elt = RARRAY_AREF(ary, i); + rb_hash_add_new_element(hash, elt, elt); } return hash; } @@ -5177,8 +5551,8 @@ ary_add_hash_by(VALUE hash, VALUE ary) long i; for (i = 0; i < RARRAY_LEN(ary); ++i) { - VALUE v = rb_ary_elt(ary, i), k = rb_yield(v); - rb_hash_add_new_element(hash, k, v); + VALUE v = rb_ary_elt(ary, i), k = rb_yield(v); + rb_hash_add_new_element(hash, k, v); } return hash; } @@ -5196,7 +5570,7 @@ ary_recycle_hash(VALUE hash) assert(RBASIC_CLASS(hash) == 0); if (RHASH_ST_TABLE_P(hash)) { st_table *tbl = RHASH_ST_TABLE(hash); - st_free_table(tbl); + st_free_table(tbl); RHASH_ST_CLEAR(hash); } } @@ -5209,6 +5583,7 @@ ary_recycle_hash(VALUE hash) * that are not found in \Array +other_array+; * items are compared using <tt>eql?</tt>; * the order from +array+ is preserved: + * * [0, 1, 1, 2, 1, 1, 3, 1, 1] - [1] # => [0, 2, 3] * [0, 1, 2, 3] - [3, 0] # => [1, 2] * [0, 1, 2] - [4] # => [0, 1, 2] @@ -5216,7 +5591,7 @@ ary_recycle_hash(VALUE hash) * Related: Array#difference. */ -static VALUE +VALUE rb_ary_diff(VALUE ary1, VALUE ary2) { VALUE ary3; @@ -5228,18 +5603,18 @@ rb_ary_diff(VALUE ary1, VALUE ary2) ary3 = rb_ary_new(); if (RARRAY_LEN(ary1) <= SMALL_ARRAY_LEN || RARRAY_LEN(ary2) <= SMALL_ARRAY_LEN) { - for (i=0; i<RARRAY_LEN(ary1); i++) { - VALUE elt = rb_ary_elt(ary1, i); - if (rb_ary_includes_by_eql(ary2, elt)) continue; - rb_ary_push(ary3, elt); - } - return ary3; + for (i=0; i<RARRAY_LEN(ary1); i++) { + VALUE elt = rb_ary_elt(ary1, i); + if (rb_ary_includes_by_eql(ary2, elt)) continue; + rb_ary_push(ary3, elt); + } + return ary3; } hash = ary_make_hash(ary2); for (i=0; i<RARRAY_LEN(ary1); i++) { if (rb_hash_stlike_lookup(hash, RARRAY_AREF(ary1, i), NULL)) continue; - rb_ary_push(ary3, rb_ary_elt(ary1, i)); + rb_ary_push(ary3, rb_ary_elt(ary1, i)); } ary_recycle_hash(hash); return ary3; @@ -5252,6 +5627,7 @@ rb_ary_diff(VALUE ary1, VALUE ary2) * Returns a new \Array containing only those elements from +self+ * that are not found in any of the Arrays +other_arrays+; * items are compared using <tt>eql?</tt>; order from +self+ is preserved: + * * [0, 1, 1, 2, 1, 1, 3, 1, 1].difference([1]) # => [0, 2, 3] * [0, 1, 2, 3].difference([3, 0], [1, 3]) # => [2] * [0, 1, 2].difference([4]) # => [0, 1, 2] @@ -5304,10 +5680,12 @@ rb_ary_difference_multi(int argc, VALUE *argv, VALUE ary) * * Returns a new \Array containing each element found in both +array+ and \Array +other_array+; * duplicates are omitted; items are compared using <tt>eql?</tt>: + * * [0, 1, 2, 3] & [1, 2] # => [1, 2] * [0, 1, 0, 1] & [0, 1] # => [0, 1] * * Preserves order from +array+: + * * [0, 1, 2] & [3, 2, 1, 0] # => [0, 1, 2] * * Related: Array#intersection. @@ -5326,23 +5704,23 @@ rb_ary_and(VALUE ary1, VALUE ary2) if (RARRAY_LEN(ary1) == 0 || RARRAY_LEN(ary2) == 0) return ary3; if (RARRAY_LEN(ary1) <= SMALL_ARRAY_LEN && RARRAY_LEN(ary2) <= SMALL_ARRAY_LEN) { - for (i=0; i<RARRAY_LEN(ary1); i++) { - v = RARRAY_AREF(ary1, i); - if (!rb_ary_includes_by_eql(ary2, v)) continue; - if (rb_ary_includes_by_eql(ary3, v)) continue; - rb_ary_push(ary3, v); - } - return ary3; + for (i=0; i<RARRAY_LEN(ary1); i++) { + v = RARRAY_AREF(ary1, i); + if (!rb_ary_includes_by_eql(ary2, v)) continue; + if (rb_ary_includes_by_eql(ary3, v)) continue; + rb_ary_push(ary3, v); + } + return ary3; } hash = ary_make_hash(ary2); for (i=0; i<RARRAY_LEN(ary1); i++) { - v = RARRAY_AREF(ary1, i); - vv = (st_data_t)v; + v = RARRAY_AREF(ary1, i); + vv = (st_data_t)v; if (rb_hash_stlike_delete(hash, &vv, 0)) { - rb_ary_push(ary3, v); - } + rb_ary_push(ary3, v); + } } ary_recycle_hash(hash); @@ -5356,10 +5734,12 @@ rb_ary_and(VALUE ary1, VALUE ary2) * Returns a new \Array containing each element found both in +self+ * and in all of the given Arrays +other_arrays+; * duplicates are omitted; items are compared using <tt>eql?</tt>: + * * [0, 1, 2, 3].intersection([0, 1, 2], [0, 1, 3]) # => [0, 1] * [0, 0, 1, 1, 2, 3].intersection([0, 1, 2], [0, 1, 3]) # => [0, 1] * * Preserves order from +self+: + * * [0, 1, 2].intersection([2, 1, 0]) # => [0, 1, 2] * * Returns a copy of +self+ if no arguments given. @@ -5418,6 +5798,7 @@ rb_ary_union_hash(VALUE hash, VALUE ary2) * Returns the union of +array+ and \Array +other_array+; * duplicates are removed; order is preserved; * items are compared using <tt>eql?</tt>: + * * [0, 1] | [2, 3] # => [0, 1, 2, 3] * [0, 1, 1] | [2, 2, 3] # => [0, 1, 2, 3] * [0, 1, 2] | [3, 2, 1, 0] # => [0, 1, 2, 3] @@ -5432,10 +5813,10 @@ rb_ary_or(VALUE ary1, VALUE ary2) ary2 = to_ary(ary2); if (RARRAY_LEN(ary1) + RARRAY_LEN(ary2) <= SMALL_ARRAY_LEN) { - ary3 = rb_ary_new(); + ary3 = rb_ary_new(); rb_ary_union(ary3, ary1); rb_ary_union(ary3, ary2); - return ary3; + return ary3; } hash = ary_make_hash(ary1); @@ -5452,6 +5833,7 @@ rb_ary_or(VALUE ary1, VALUE ary2) * * Returns a new \Array that is the union of +self+ and all given Arrays +other_arrays+; * duplicates are removed; order is preserved; items are compared using <tt>eql?</tt>: + * * [0, 1, 2, 3].union([4, 5], [6, 7]) # => [0, 1, 2, 3, 4, 5, 6, 7] * [0, 1, 1].union([2, 1], [3, 1]) # => [0, 1, 2, 3] * [0, 1, 2, 3].union([3, 2], [1, 0]) # => [0, 1, 2, 3] @@ -5496,13 +5878,14 @@ rb_ary_union_multi(int argc, VALUE *argv, VALUE ary) * ary.intersect?(other_ary) -> true or false * * Returns +true+ if the array and +other_ary+ have at least one element in - * common, otherwise returns +false+. + * common, otherwise returns +false+: * * a = [ 1, 2, 3 ] * b = [ 3, 4, 5 ] * c = [ 5, 6, 7 ] * a.intersect?(b) #=> true * a.intersect?(c) #=> false + * */ static VALUE @@ -5643,6 +6026,7 @@ ary_max_opt_string(VALUE ary, long i, VALUE vmax) * array.max(n) {|a, b| ... } -> new_array * * Returns one of the following: + * * - The maximum-valued element from +self+. * - A new \Array of maximum-valued elements selected from +self+. * @@ -5651,10 +6035,12 @@ ary_max_opt_string(VALUE ary, long i, VALUE vmax) * * With no argument and no block, returns the element in +self+ * having the maximum value per method <tt><=></tt>: + * * [0, 1, 2].max # => 2 * * With an argument \Integer +n+ and no block, returns a new \Array with at most +n+ elements, * in descending order per method <tt><=></tt>: + * * [0, 1, 2, 3].max(3) # => [3, 2, 1] * [0, 1, 2, 3].max(6) # => [3, 2, 1, 0] * @@ -5662,16 +6048,18 @@ ary_max_opt_string(VALUE ary, long i, VALUE vmax) * * With a block and no argument, calls the block <tt>self.size-1</tt> times to compare elements; * returns the element having the maximum value per the block: + * * ['0', '00', '000'].max {|a, b| a.size <=> b.size } # => "000" * * With an argument +n+ and a block, returns a new \Array with at most +n+ elements, * in descending order per the block: + * * ['0', '00', '000'].max(2) {|a, b| a.size <=> b.size } # => ["000", "00"] + * */ static VALUE rb_ary_max(int argc, VALUE *argv, VALUE ary) { - struct cmp_opt_data cmp_opt = { 0, 0 }; VALUE result = Qundef, v; VALUE num; long i; @@ -5681,23 +6069,23 @@ rb_ary_max(int argc, VALUE *argv, VALUE ary) const long n = RARRAY_LEN(ary); if (rb_block_given_p()) { - for (i = 0; i < RARRAY_LEN(ary); i++) { - v = RARRAY_AREF(ary, i); - if (result == Qundef || rb_cmpint(rb_yield_values(2, v, result), v, result) > 0) { - result = v; - } - } + for (i = 0; i < RARRAY_LEN(ary); i++) { + v = RARRAY_AREF(ary, i); + if (UNDEF_P(result) || rb_cmpint(rb_yield_values(2, v, result), v, result) > 0) { + result = v; + } + } } else if (n > 0) { result = RARRAY_AREF(ary, 0); if (n > 1) { - if (FIXNUM_P(result) && CMP_OPTIMIZABLE(cmp_opt, Integer)) { + if (FIXNUM_P(result) && CMP_OPTIMIZABLE(INTEGER)) { return ary_max_opt_fixnum(ary, 1, result); } - else if (STRING_P(result) && CMP_OPTIMIZABLE(cmp_opt, String)) { + else if (STRING_P(result) && CMP_OPTIMIZABLE(STRING)) { return ary_max_opt_string(ary, 1, result); } - else if (RB_FLOAT_TYPE_P(result) && CMP_OPTIMIZABLE(cmp_opt, Float)) { + else if (RB_FLOAT_TYPE_P(result) && CMP_OPTIMIZABLE(FLOAT)) { return ary_max_opt_float(ary, 1, result); } else { @@ -5705,7 +6093,7 @@ rb_ary_max(int argc, VALUE *argv, VALUE ary) } } } - if (result == Qundef) return Qnil; + if (UNDEF_P(result)) return Qnil; return result; } @@ -5806,6 +6194,7 @@ ary_min_opt_string(VALUE ary, long i, VALUE vmin) * array.min(n) { |a, b| ... } -> new_array * * Returns one of the following: + * * - The minimum-valued element from +self+. * - A new \Array of minimum-valued elements selected from +self+. * @@ -5814,10 +6203,12 @@ ary_min_opt_string(VALUE ary, long i, VALUE vmin) * * With no argument and no block, returns the element in +self+ * having the minimum value per method <tt><=></tt>: + * * [0, 1, 2].min # => 0 * * With \Integer argument +n+ and no block, returns a new \Array with at most +n+ elements, * in ascending order per method <tt><=></tt>: + * * [0, 1, 2, 3].min(3) # => [0, 1, 2] * [0, 1, 2, 3].min(6) # => [0, 1, 2, 3] * @@ -5825,16 +6216,18 @@ ary_min_opt_string(VALUE ary, long i, VALUE vmin) * * With a block and no argument, calls the block <tt>self.size-1</tt> times to compare elements; * returns the element having the minimum value per the block: + * * ['0', '00', '000'].min { |a, b| a.size <=> b.size } # => "0" * * With an argument +n+ and a block, returns a new \Array with at most +n+ elements, * in ascending order per the block: + * * ['0', '00', '000'].min(2) {|a, b| a.size <=> b.size } # => ["0", "00"] + * */ static VALUE rb_ary_min(int argc, VALUE *argv, VALUE ary) { - struct cmp_opt_data cmp_opt = { 0, 0 }; VALUE result = Qundef, v; VALUE num; long i; @@ -5844,23 +6237,23 @@ rb_ary_min(int argc, VALUE *argv, VALUE ary) const long n = RARRAY_LEN(ary); if (rb_block_given_p()) { - for (i = 0; i < RARRAY_LEN(ary); i++) { - v = RARRAY_AREF(ary, i); - if (result == Qundef || rb_cmpint(rb_yield_values(2, v, result), v, result) < 0) { - result = v; - } - } + for (i = 0; i < RARRAY_LEN(ary); i++) { + v = RARRAY_AREF(ary, i); + if (UNDEF_P(result) || rb_cmpint(rb_yield_values(2, v, result), v, result) < 0) { + result = v; + } + } } else if (n > 0) { result = RARRAY_AREF(ary, 0); if (n > 1) { - if (FIXNUM_P(result) && CMP_OPTIMIZABLE(cmp_opt, Integer)) { + if (FIXNUM_P(result) && CMP_OPTIMIZABLE(INTEGER)) { return ary_min_opt_fixnum(ary, 1, result); } - else if (STRING_P(result) && CMP_OPTIMIZABLE(cmp_opt, String)) { + else if (STRING_P(result) && CMP_OPTIMIZABLE(STRING)) { return ary_min_opt_string(ary, 1, result); } - else if (RB_FLOAT_TYPE_P(result) && CMP_OPTIMIZABLE(cmp_opt, Float)) { + else if (RB_FLOAT_TYPE_P(result) && CMP_OPTIMIZABLE(FLOAT)) { return ary_min_opt_float(ary, 1, result); } else { @@ -5868,7 +6261,7 @@ rb_ary_min(int argc, VALUE *argv, VALUE ary) } } } - if (result == Qundef) return Qnil; + if (UNDEF_P(result)) return Qnil; return result; } @@ -5884,13 +6277,16 @@ rb_ary_min(int argc, VALUE *argv, VALUE ary) * with an \Integer; * returns a new 2-element \Array containing the minimum and maximum values * from +self+, per method <tt><=></tt>: + * * [0, 1, 2].minmax # => [0, 2] * * When a block is given, the block must return an \Integer; * the block is called <tt>self.size-1</tt> times to compare elements; * returns a new 2-element \Array containing the minimum and maximum values * from +self+, per the block: + * * ['0', '00', '000'].minmax {|a, b| a.size <=> b.size } # => ["0", "000"] + * */ static VALUE rb_ary_minmax(VALUE ary) @@ -5920,6 +6316,7 @@ push_value(st_data_t key, st_data_t val, st_data_t ary) * to compare. * * Returns +self+ if any elements removed: + * * a = [0, 0, 1, 1, 2, 2] * a.uniq! # => [0, 1, 2] * @@ -5930,6 +6327,7 @@ push_value(st_data_t key, st_data_t val, st_data_t ary) * elements for which the block returns duplicate values. * * Returns +self+ if any elements removed: + * * a = ['a', 'aa', 'aaa', 'b', 'bb', 'bbb'] * a.uniq! {|element| element.size } # => ['a', 'aa', 'aaa'] * @@ -5945,19 +6343,19 @@ rb_ary_uniq_bang(VALUE ary) if (RARRAY_LEN(ary) <= 1) return Qnil; if (rb_block_given_p()) - hash = ary_make_hash_by(ary); + hash = ary_make_hash_by(ary); else - hash = ary_make_hash(ary); + hash = ary_make_hash(ary); hash_size = RHASH_SIZE(hash); if (RARRAY_LEN(ary) == hash_size) { - return Qnil; + return Qnil; } rb_ary_modify_check(ary); ARY_SET_LEN(ary, 0); if (ARY_SHARED_P(ary) && !ARY_EMBED_P(ary)) { - rb_ary_unshare(ary); - FL_SET_EMBED(ary); + rb_ary_unshare(ary); + FL_SET_EMBED(ary); } ary_resize_capa(ary, hash_size); rb_hash_foreach(hash, push_value, ary); @@ -5975,15 +6373,18 @@ rb_ary_uniq_bang(VALUE ary) * the first occurrence always being retained. * * With no block given, identifies and omits duplicates using method <tt>eql?</tt> - * to compare. + * to compare: + * * a = [0, 0, 1, 1, 2, 2] * a.uniq # => [0, 1, 2] * * With a block given, calls the block for each element; * identifies (using method <tt>eql?</tt>) and omits duplicate values, * that is, those elements for which the block returns the same value: + * * a = ['a', 'aa', 'aaa', 'b', 'bb', 'bbb'] * a.uniq {|element| element.size } # => ["a", "aa", "aaa"] + * */ static VALUE @@ -5996,12 +6397,12 @@ rb_ary_uniq(VALUE ary) uniq = rb_ary_dup(ary); } else if (rb_block_given_p()) { - hash = ary_make_hash_by(ary); - uniq = rb_hash_values(hash); + hash = ary_make_hash_by(ary); + uniq = rb_hash_values(hash); } else { - hash = ary_make_hash(ary); - uniq = rb_hash_values(hash); + hash = ary_make_hash(ary); + uniq = rb_hash_values(hash); } if (hash) { ary_recycle_hash(hash); @@ -6030,12 +6431,12 @@ rb_ary_compact_bang(VALUE ary) end = p + RARRAY_LEN(ary); while (t < end) { - if (NIL_P(*t)) t++; - else *p++ = *t++; + if (NIL_P(*t)) t++; + else *p++ = *t++; } n = p - RARRAY_CONST_PTR_TRANSIENT(ary); if (RARRAY_LEN(ary) == n) { - return Qnil; + return Qnil; } ary_resize_smaller(ary, n); @@ -6047,6 +6448,7 @@ rb_ary_compact_bang(VALUE ary) * array.compact -> new_array * * Returns a new \Array containing all non-+nil+ elements from +self+: + * * a = [nil, 0, nil, 1, nil, 2, nil] * a.compact # => [0, 1, 2] */ @@ -6068,19 +6470,22 @@ rb_ary_compact(VALUE ary) * Returns a count of specified elements. * * With no argument and no block, returns the count of all elements: + * * [0, 1, 2].count # => 3 * [].count # => 0 * * With argument +obj+, returns the count of elements <tt>==</tt> to +obj+: + * * [0, 1, 2, 0.0].count(0) # => 2 * [0, 1, 2].count(3) # => 0 * * With no argument and a block given, calls the block with each element; * returns the count of elements for which the block returns a truthy value: + * * [0, 1, 2, 3].count {|element| element > 1} # => 2 * * With argument +obj+ and a block given, issues a warning, ignores the block, - * and returns the count of elements <tt>==</tt> to +obj+: + * and returns the count of elements <tt>==</tt> to +obj+. */ static VALUE @@ -6089,25 +6494,25 @@ rb_ary_count(int argc, VALUE *argv, VALUE ary) long i, n = 0; if (rb_check_arity(argc, 0, 1) == 0) { - VALUE v; + VALUE v; - if (!rb_block_given_p()) - return LONG2NUM(RARRAY_LEN(ary)); + if (!rb_block_given_p()) + return LONG2NUM(RARRAY_LEN(ary)); - for (i = 0; i < RARRAY_LEN(ary); i++) { - v = RARRAY_AREF(ary, i); - if (RTEST(rb_yield(v))) n++; - } + for (i = 0; i < RARRAY_LEN(ary); i++) { + v = RARRAY_AREF(ary, i); + if (RTEST(rb_yield(v))) n++; + } } else { VALUE obj = argv[0]; - if (rb_block_given_p()) { - rb_warn("given block not used"); - } - for (i = 0; i < RARRAY_LEN(ary); i++) { - if (rb_equal(RARRAY_AREF(ary, i), obj)) n++; - } + if (rb_block_given_p()) { + rb_warn("given block not used"); + } + for (i = 0; i < RARRAY_LEN(ary); i++) { + if (rb_equal(RARRAY_AREF(ary, i), obj)) n++; + } } return LONG2NUM(n); @@ -6141,64 +6546,64 @@ flatten(VALUE ary, int level) rb_ary_push(stack, LONG2NUM(i + 1)); if (level < 0) { - vmemo = rb_hash_new(); - RBASIC_CLEAR_CLASS(vmemo); - memo = st_init_numtable(); - rb_hash_st_table_set(vmemo, memo); - st_insert(memo, (st_data_t)ary, (st_data_t)Qtrue); - st_insert(memo, (st_data_t)tmp, (st_data_t)Qtrue); + vmemo = rb_hash_new(); + RBASIC_CLEAR_CLASS(vmemo); + memo = st_init_numtable(); + rb_hash_st_table_set(vmemo, memo); + st_insert(memo, (st_data_t)ary, (st_data_t)Qtrue); + st_insert(memo, (st_data_t)tmp, (st_data_t)Qtrue); } ary = tmp; i = 0; while (1) { - while (i < RARRAY_LEN(ary)) { - elt = RARRAY_AREF(ary, i++); - if (level >= 0 && RARRAY_LEN(stack) / 2 >= level) { - rb_ary_push(result, elt); - continue; - } - tmp = rb_check_array_type(elt); - if (RBASIC(result)->klass) { - if (memo) { - RB_GC_GUARD(vmemo); - st_clear(memo); - } - rb_raise(rb_eRuntimeError, "flatten reentered"); - } - if (NIL_P(tmp)) { - rb_ary_push(result, elt); - } - else { - if (memo) { - id = (st_data_t)tmp; - if (st_is_member(memo, id)) { - st_clear(memo); - rb_raise(rb_eArgError, "tried to flatten recursive array"); - } - st_insert(memo, id, (st_data_t)Qtrue); - } - rb_ary_push(stack, ary); - rb_ary_push(stack, LONG2NUM(i)); - ary = tmp; - i = 0; - } - } - if (RARRAY_LEN(stack) == 0) { - break; - } - if (memo) { - id = (st_data_t)ary; - st_delete(memo, &id, 0); - } - tmp = rb_ary_pop(stack); - i = NUM2LONG(tmp); - ary = rb_ary_pop(stack); + while (i < RARRAY_LEN(ary)) { + elt = RARRAY_AREF(ary, i++); + if (level >= 0 && RARRAY_LEN(stack) / 2 >= level) { + rb_ary_push(result, elt); + continue; + } + tmp = rb_check_array_type(elt); + if (RBASIC(result)->klass) { + if (memo) { + RB_GC_GUARD(vmemo); + st_clear(memo); + } + rb_raise(rb_eRuntimeError, "flatten reentered"); + } + if (NIL_P(tmp)) { + rb_ary_push(result, elt); + } + else { + if (memo) { + id = (st_data_t)tmp; + if (st_is_member(memo, id)) { + st_clear(memo); + rb_raise(rb_eArgError, "tried to flatten recursive array"); + } + st_insert(memo, id, (st_data_t)Qtrue); + } + rb_ary_push(stack, ary); + rb_ary_push(stack, LONG2NUM(i)); + ary = tmp; + i = 0; + } + } + if (RARRAY_LEN(stack) == 0) { + break; + } + if (memo) { + id = (st_data_t)ary; + st_delete(memo, &id, 0); + } + tmp = rb_ary_pop(stack); + i = NUM2LONG(tmp); + ary = rb_ary_pop(stack); } if (memo) { - st_clear(memo); + st_clear(memo); } RBASIC_SET_CLASS(result, rb_cArray); @@ -6214,6 +6619,7 @@ flatten(VALUE ary, int level) * returns +self+ if any changes, +nil+ otherwise. * * With non-negative \Integer argument +level+, flattens recursively through +level+ levels: + * * a = [ 0, [ 1, [2, 3], 4 ], 5 ] * a.flatten!(1) # => [0, 1, [2, 3], 4, 5] * a = [ 0, [ 1, [2, 3], 4 ], 5 ] @@ -6223,6 +6629,7 @@ flatten(VALUE ary, int level) * [0, 1, 2].flatten!(1) # => nil * * With no argument, a +nil+ argument, or with negative argument +level+, flattens all levels: + * * a = [ 0, [ 1, [2, 3], 4 ], 5 ] * a.flatten! # => [0, 1, 2, 3, 4, 5] * [0, 1, 2].flatten! # => nil @@ -6231,6 +6638,7 @@ flatten(VALUE ary, int level) * a = [ 0, [ 1, [2, 3], 4 ], 5 ] * a.flatten!(-2) # => [0, 1, 2, 3, 4, 5] * [0, 1, 2].flatten!(-1) # => nil + * */ static VALUE @@ -6246,7 +6654,7 @@ rb_ary_flatten_bang(int argc, VALUE *argv, VALUE ary) result = flatten(ary, level); if (result == ary) { - return Qnil; + return Qnil; } if (!(mod = ARY_EMBED_P(result))) rb_obj_freeze(result); rb_ary_replace(ary, result); @@ -6265,6 +6673,7 @@ rb_ary_flatten_bang(int argc, VALUE *argv, VALUE ary) * - Each \Array is replaced by its individual elements. * * With non-negative \Integer argument +level+, flattens recursively through +level+ levels: + * * a = [ 0, [ 1, [2, 3], 4 ], 5 ] * a.flatten(0) # => [0, [1, [2, 3], 4], 5] * a = [ 0, [ 1, [2, 3], 4 ], 5 ] @@ -6275,6 +6684,7 @@ rb_ary_flatten_bang(int argc, VALUE *argv, VALUE ary) * a.flatten(3) # => [0, 1, 2, 3, 4, 5] * * With no argument, a +nil+ argument, or with negative argument +level+, flattens all levels: + * * a = [ 0, [ 1, [2, 3], 4 ], 5 ] * a.flatten # => [0, 1, 2, 3, 4, 5] * [0, 1, 2].flatten # => [0, 1, 2] @@ -6283,6 +6693,7 @@ rb_ary_flatten_bang(int argc, VALUE *argv, VALUE ary) * a = [ 0, [ 1, [2, 3], 4 ], 5 ] * a.flatten(-2) # => [0, 1, 2, 3, 4, 5] * [0, 1, 2].flatten(-1) # => [0, 1, 2] + * */ static VALUE @@ -6314,16 +6725,16 @@ rb_ary_shuffle_bang(rb_execution_context_t *ec, VALUE ary, VALUE randgen) rb_ary_modify(ary); i = len = RARRAY_LEN(ary); RARRAY_PTR_USE(ary, ptr, { - while (i) { - long j = RAND_UPTO(i); - VALUE tmp; + while (i) { + long j = RAND_UPTO(i); + VALUE tmp; if (len != RARRAY_LEN(ary) || ptr != RARRAY_CONST_PTR_TRANSIENT(ary)) { rb_raise(rb_eRuntimeError, "modified during shuffle"); - } - tmp = ptr[--i]; - ptr[i] = ptr[j]; - ptr[j] = tmp; - } + } + tmp = ptr[--i]; + ptr[i] = ptr[j]; + ptr[j] = tmp; + } }); /* WB: no new reference */ return ary; } @@ -6346,120 +6757,120 @@ ary_sample(rb_execution_context_t *ec, VALUE ary, VALUE randgen, VALUE nv, VALUE len = RARRAY_LEN(ary); if (!to_array) { - if (len < 2) - i = 0; - else - i = RAND_UPTO(len); + if (len < 2) + i = 0; + else + i = RAND_UPTO(len); - return rb_ary_elt(ary, i); + return rb_ary_elt(ary, i); } n = NUM2LONG(nv); if (n < 0) rb_raise(rb_eArgError, "negative sample number"); if (n > len) n = len; if (n <= numberof(idx)) { - for (i = 0; i < n; ++i) { - rnds[i] = RAND_UPTO(len - i); - } + for (i = 0; i < n; ++i) { + rnds[i] = RAND_UPTO(len - i); + } } k = len; len = RARRAY_LEN(ary); if (len < k && n <= numberof(idx)) { - for (i = 0; i < n; ++i) { - if (rnds[i] >= len) return rb_ary_new_capa(0); - } + for (i = 0; i < n; ++i) { + if (rnds[i] >= len) return rb_ary_new_capa(0); + } } if (n > len) n = len; switch (n) { case 0: - return rb_ary_new_capa(0); + return rb_ary_new_capa(0); case 1: - i = rnds[0]; - return rb_ary_new_from_args(1, RARRAY_AREF(ary, i)); + i = rnds[0]; + return rb_ary_new_from_args(1, RARRAY_AREF(ary, i)); case 2: - i = rnds[0]; - j = rnds[1]; - if (j >= i) j++; - return rb_ary_new_from_args(2, RARRAY_AREF(ary, i), RARRAY_AREF(ary, j)); + i = rnds[0]; + j = rnds[1]; + if (j >= i) j++; + return rb_ary_new_from_args(2, RARRAY_AREF(ary, i), RARRAY_AREF(ary, j)); case 3: - i = rnds[0]; - j = rnds[1]; - k = rnds[2]; - { - long l = j, g = i; - if (j >= i) l = i, g = ++j; - if (k >= l && (++k >= g)) ++k; - } - return rb_ary_new_from_args(3, RARRAY_AREF(ary, i), RARRAY_AREF(ary, j), RARRAY_AREF(ary, k)); + i = rnds[0]; + j = rnds[1]; + k = rnds[2]; + { + long l = j, g = i; + if (j >= i) l = i, g = ++j; + if (k >= l && (++k >= g)) ++k; + } + return rb_ary_new_from_args(3, RARRAY_AREF(ary, i), RARRAY_AREF(ary, j), RARRAY_AREF(ary, k)); } memo_threshold = - len < 2560 ? len / 128 : - len < 5120 ? len / 64 : - len < 10240 ? len / 32 : - len / 16; + len < 2560 ? len / 128 : + len < 5120 ? len / 64 : + len < 10240 ? len / 32 : + len / 16; if (n <= numberof(idx)) { - long sorted[numberof(idx)]; - sorted[0] = idx[0] = rnds[0]; - for (i=1; i<n; i++) { - k = rnds[i]; - for (j = 0; j < i; ++j) { - if (k < sorted[j]) break; - ++k; - } - memmove(&sorted[j+1], &sorted[j], sizeof(sorted[0])*(i-j)); - sorted[j] = idx[i] = k; - } - result = rb_ary_new_capa(n); + long sorted[numberof(idx)]; + sorted[0] = idx[0] = rnds[0]; + for (i=1; i<n; i++) { + k = rnds[i]; + for (j = 0; j < i; ++j) { + if (k < sorted[j]) break; + ++k; + } + memmove(&sorted[j+1], &sorted[j], sizeof(sorted[0])*(i-j)); + sorted[j] = idx[i] = k; + } + result = rb_ary_new_capa(n); RARRAY_PTR_USE_TRANSIENT(result, ptr_result, { - for (i=0; i<n; i++) { - ptr_result[i] = RARRAY_AREF(ary, idx[i]); - } - }); + for (i=0; i<n; i++) { + ptr_result[i] = RARRAY_AREF(ary, idx[i]); + } + }); } else if (n <= memo_threshold / 2) { - long max_idx = 0; + long max_idx = 0; #undef RUBY_UNTYPED_DATA_WARNING #define RUBY_UNTYPED_DATA_WARNING 0 - VALUE vmemo = Data_Wrap_Struct(0, 0, st_free_table, 0); - st_table *memo = st_init_numtable_with_size(n); - DATA_PTR(vmemo) = memo; - result = rb_ary_new_capa(n); - RARRAY_PTR_USE(result, ptr_result, { - for (i=0; i<n; i++) { - long r = RAND_UPTO(len-i) + i; - ptr_result[i] = r; - if (r > max_idx) max_idx = r; - } - len = RARRAY_LEN(ary); - if (len <= max_idx) n = 0; - else if (n > len) n = len; + VALUE vmemo = Data_Wrap_Struct(0, 0, st_free_table, 0); + st_table *memo = st_init_numtable_with_size(n); + DATA_PTR(vmemo) = memo; + result = rb_ary_new_capa(n); + RARRAY_PTR_USE(result, ptr_result, { + for (i=0; i<n; i++) { + long r = RAND_UPTO(len-i) + i; + ptr_result[i] = r; + if (r > max_idx) max_idx = r; + } + len = RARRAY_LEN(ary); + if (len <= max_idx) n = 0; + else if (n > len) n = len; RARRAY_PTR_USE_TRANSIENT(ary, ptr_ary, { - for (i=0; i<n; i++) { - long j2 = j = ptr_result[i]; - long i2 = i; - st_data_t value; - if (st_lookup(memo, (st_data_t)i, &value)) i2 = (long)value; - if (st_lookup(memo, (st_data_t)j, &value)) j2 = (long)value; - st_insert(memo, (st_data_t)j, (st_data_t)i2); - ptr_result[i] = ptr_ary[j2]; - } - }); - }); - DATA_PTR(vmemo) = 0; - st_free_table(memo); + for (i=0; i<n; i++) { + long j2 = j = ptr_result[i]; + long i2 = i; + st_data_t value; + if (st_lookup(memo, (st_data_t)i, &value)) i2 = (long)value; + if (st_lookup(memo, (st_data_t)j, &value)) j2 = (long)value; + st_insert(memo, (st_data_t)j, (st_data_t)i2); + ptr_result[i] = ptr_ary[j2]; + } + }); + }); + DATA_PTR(vmemo) = 0; + st_free_table(memo); } else { - result = rb_ary_dup(ary); - RBASIC_CLEAR_CLASS(result); - RB_GC_GUARD(ary); - RARRAY_PTR_USE(result, ptr_result, { - for (i=0; i<n; i++) { - j = RAND_UPTO(len-i) + i; - nv = ptr_result[j]; - ptr_result[j] = ptr_result[i]; - ptr_result[i] = nv; - } - }); - RBASIC_SET_CLASS_RAW(result, rb_cArray); + result = rb_ary_dup(ary); + RBASIC_CLEAR_CLASS(result); + RB_GC_GUARD(ary); + RARRAY_PTR_USE(result, ptr_result, { + for (i=0; i<n; i++) { + j = RAND_UPTO(len-i) + i; + nv = ptr_result[j]; + ptr_result[j] = ptr_result[i]; + ptr_result[i] = nv; + } + }); + RBASIC_SET_CLASS_RAW(result, rb_cArray); } ARY_SET_LEN(result, n); @@ -6478,7 +6889,7 @@ rb_ary_cycle_size(VALUE self, VALUE args, VALUE eobj) long mul; VALUE n = Qnil; if (args && (RARRAY_LEN(args) > 0)) { - n = RARRAY_AREF(args, 0); + n = RARRAY_AREF(args, 0); } if (RARRAY_LEN(self) == 0) return INT2FIX(0); if (NIL_P(n)) return DBL2NUM(HUGE_VAL); @@ -6498,15 +6909,18 @@ rb_ary_cycle_size(VALUE self, VALUE args, VALUE eobj) * When called with positive \Integer argument +count+ and a block, * calls the block with each element, then does so again, * until it has done so +count+ times; returns +nil+: + * * output = [] * [0, 1].cycle(2) {|element| output.push(element) } # => nil * output # => [0, 1, 0, 1] * * If +count+ is zero or negative, does not call the block: + * * [0, 1].cycle(0) {|element| fail 'Cannot happen' } # => nil * [0, 1].cycle(-1) {|element| fail 'Cannot happen' } # => nil * * When a block is given, and argument is omitted or +nil+, cycles forever: + * * # Prints 0 and 1 forever. * [0, 1].cycle {|element| puts element } * [0, 1].cycle(nil) {|element| puts element } @@ -6516,6 +6930,7 @@ rb_ary_cycle_size(VALUE self, VALUE args, VALUE eobj) * [0, 1].cycle(2) # => #<Enumerator: [0, 1]:cycle(2)> * [0, 1].cycle # => # => #<Enumerator: [0, 1]:cycle> * [0, 1].cycle.first(5) # => [0, 1, 0, 1, 0] + * */ static VALUE rb_ary_cycle(int argc, VALUE *argv, VALUE ary) @@ -6541,9 +6956,6 @@ rb_ary_cycle(int argc, VALUE *argv, VALUE ary) return Qnil; } -#define tmpary(n) rb_ary_tmp_new(n) -#define tmpary_discard(a) (ary_discard(a), RBASIC_SET_CLASS_RAW(a, rb_cArray)) - /* * Build a ruby array of the corresponding values and yield it to the * associated block. @@ -6579,32 +6991,32 @@ permute0(const long n, const long r, long *const p, char *const used, const VALU long i = 0, index = 0; for (;;) { - const char *const unused = memchr(&used[i], 0, n-i); - if (!unused) { - if (!index) break; - i = p[--index]; /* pop index */ - used[i++] = 0; /* index unused */ - } - else { - i = unused - used; - p[index] = i; - used[i] = 1; /* mark index used */ - ++index; - if (index < r-1) { /* if not done yet */ - p[index] = i = 0; - continue; - } - for (i = 0; i < n; ++i) { - if (used[i]) continue; - p[index] = i; - if (!yield_indexed_values(values, r, p)) { - rb_raise(rb_eRuntimeError, "permute reentered"); - } - } - i = p[--index]; /* pop index */ - used[i] = 0; /* index unused */ - p[index] = ++i; - } + const char *const unused = memchr(&used[i], 0, n-i); + if (!unused) { + if (!index) break; + i = p[--index]; /* pop index */ + used[i++] = 0; /* index unused */ + } + else { + i = unused - used; + p[index] = i; + used[i] = 1; /* mark index used */ + ++index; + if (index < r-1) { /* if not done yet */ + p[index] = i = 0; + continue; + } + for (i = 0; i < n; ++i) { + if (used[i]) continue; + p[index] = i; + if (!yield_indexed_values(values, r, p)) { + rb_raise(rb_eRuntimeError, "permute reentered"); + } + } + i = p[--index]; /* pop index */ + used[i] = 0; /* index unused */ + p[index] = ++i; + } } } @@ -6617,14 +7029,14 @@ descending_factorial(long from, long how_many) { VALUE cnt; if (how_many > 0) { - cnt = LONG2FIX(from); - while (--how_many > 0) { - long v = --from; - cnt = rb_int_mul(cnt, LONG2FIX(v)); - } + cnt = LONG2FIX(from); + while (--how_many > 0) { + long v = --from; + cnt = rb_int_mul(cnt, LONG2FIX(v)); + } } else { - cnt = LONG2FIX(how_many == 0); + cnt = LONG2FIX(how_many == 0); } return cnt; } @@ -6635,18 +7047,18 @@ binomial_coefficient(long comb, long size) VALUE r; long i; if (comb > size-comb) { - comb = size-comb; + comb = size-comb; } if (comb < 0) { - return LONG2FIX(0); + return LONG2FIX(0); } else if (comb == 0) { - return LONG2FIX(1); + return LONG2FIX(1); } r = LONG2FIX(size); for (i = 1; i < comb; ++i) { - r = rb_int_mul(r, LONG2FIX(size - i)); - r = rb_int_idiv(r, LONG2FIX(i + 1)); + r = rb_int_mul(r, LONG2FIX(size - i)); + r = rb_int_idiv(r, LONG2FIX(i + 1)); } return r; } @@ -6674,19 +7086,26 @@ rb_ary_permutation_size(VALUE ary, VALUE args, VALUE eobj) * are given, calls the block with all +n+-tuple permutations of +self+. * * Example: + * * a = [0, 1, 2] * a.permutation(2) {|permutation| p permutation } + * * Output: + * * [0, 1] * [0, 2] * [1, 0] * [1, 2] * [2, 0] * [2, 1] + * * Another example: + * * a = [0, 1, 2] * a.permutation(3) {|permutation| p permutation } + * * Output: + * * [0, 1, 2] * [0, 2, 1] * [1, 0, 2] @@ -6695,22 +7114,29 @@ rb_ary_permutation_size(VALUE ary, VALUE args, VALUE eobj) * [2, 1, 0] * * When +n+ is zero, calls the block once with a new empty \Array: + * * a = [0, 1, 2] * a.permutation(0) {|permutation| p permutation } + * * Output: + * * [] * * When +n+ is out of range (negative or larger than <tt>self.size</tt>), * does not call the block: + * * a = [0, 1, 2] * a.permutation(-1) {|permutation| fail 'Cannot happen' } * a.permutation(4) {|permutation| fail 'Cannot happen' } * * When a block given but no argument, * behaves the same as <tt>a.permutation(a.size)</tt>: + * * a = [0, 1, 2] * a.permutation {|permutation| p permutation } + * * Output: + * * [0, 1, 2] * [0, 2, 1] * [1, 0, 2] @@ -6719,9 +7145,11 @@ rb_ary_permutation_size(VALUE ary, VALUE args, VALUE eobj) * [2, 1, 0] * * Returns a new \Enumerator if no block given: + * * a = [0, 1, 2] * a.permutation # => #<Enumerator: [0, 1, 2]:permutation> * a.permutation(2) # => #<Enumerator: [0, 1, 2]:permutation(2)> + * */ static VALUE @@ -6736,28 +7164,28 @@ rb_ary_permutation(int argc, VALUE *argv, VALUE ary) r = NUM2LONG(argv[0]); /* Permutation size from argument */ if (r < 0 || n < r) { - /* no permutations: yield nothing */ + /* no permutations: yield nothing */ } else if (r == 0) { /* exactly one permutation: the zero-length array */ - rb_yield(rb_ary_new2(0)); + rb_yield(rb_ary_new2(0)); } else if (r == 1) { /* this is a special, easy case */ - for (i = 0; i < RARRAY_LEN(ary); i++) { - rb_yield(rb_ary_new3(1, RARRAY_AREF(ary, i))); - } + for (i = 0; i < RARRAY_LEN(ary); i++) { + rb_yield(rb_ary_new3(1, RARRAY_AREF(ary, i))); + } } else { /* this is the general case */ - volatile VALUE t0; - long *p = ALLOCV_N(long, t0, r+roomof(n, sizeof(long))); - char *used = (char*)(p + r); - VALUE ary0 = ary_make_shared_copy(ary); /* private defensive copy of ary */ - RBASIC_CLEAR_CLASS(ary0); + volatile VALUE t0; + long *p = ALLOCV_N(long, t0, r+roomof(n, sizeof(long))); + char *used = (char*)(p + r); + VALUE ary0 = ary_make_shared_copy(ary); /* private defensive copy of ary */ + RBASIC_CLEAR_CLASS(ary0); - MEMZERO(used, char, n); /* initialize array */ + MEMZERO(used, char, n); /* initialize array */ - permute0(n, r, p, used, ary0); /* compute and yield permutations */ - ALLOCV_END(t0); - RBASIC_SET_CLASS_RAW(ary0, rb_cArray); + permute0(n, r, p, used, ary0); /* compute and yield permutations */ + ALLOCV_END(t0); + RBASIC_SET_CLASS_RAW(ary0, rb_cArray); } return ary; } @@ -6770,16 +7198,16 @@ combinate0(const long len, const long n, long *const stack, const VALUE values) MEMZERO(stack+1, long, n); stack[0] = -1; for (;;) { - for (lev++; lev < n; lev++) { - stack[lev+1] = stack[lev]+1; - } - if (!yield_indexed_values(values, n, stack+1)) { - rb_raise(rb_eRuntimeError, "combination reentered"); - } - do { - if (lev == 0) return; - stack[lev--]++; - } while (stack[lev+1]+n == len+lev+1); + for (lev++; lev < n; lev++) { + stack[lev+1] = stack[lev]+1; + } + if (!yield_indexed_values(values, n, stack+1)) { + rb_raise(rb_eRuntimeError, "combination reentered"); + } + do { + if (lev == 0) return; + stack[lev--]++; + } while (stack[lev+1]+n == len+lev+1); } } @@ -6804,34 +7232,46 @@ rb_ary_combination_size(VALUE ary, VALUE args, VALUE eobj) * are given, calls the block with all +n+-tuple combinations of +self+. * * Example: + * * a = [0, 1, 2] * a.combination(2) {|combination| p combination } + * * Output: + * * [0, 1] * [0, 2] * [1, 2] * * Another example: + * * a = [0, 1, 2] * a.combination(3) {|combination| p combination } + * * Output: + * * [0, 1, 2] * * When +n+ is zero, calls the block once with a new empty \Array: + * * a = [0, 1, 2] * a1 = a.combination(0) {|combination| p combination } + * * Output: + * * [] * * When +n+ is out of range (negative or larger than <tt>self.size</tt>), * does not call the block: + * * a = [0, 1, 2] * a.combination(-1) {|combination| fail 'Cannot happen' } * a.combination(4) {|combination| fail 'Cannot happen' } * * Returns a new \Enumerator if no block given: + * * a = [0, 1, 2] * a.combination(2) # => #<Enumerator: [0, 1, 2]:combination(2)> + * */ static VALUE @@ -6843,25 +7283,25 @@ rb_ary_combination(VALUE ary, VALUE num) RETURN_SIZED_ENUMERATOR(ary, 1, &num, rb_ary_combination_size); len = RARRAY_LEN(ary); if (n < 0 || len < n) { - /* yield nothing */ + /* yield nothing */ } else if (n == 0) { - rb_yield(rb_ary_new2(0)); + rb_yield(rb_ary_new2(0)); } else if (n == 1) { - for (i = 0; i < RARRAY_LEN(ary); i++) { - rb_yield(rb_ary_new3(1, RARRAY_AREF(ary, i))); - } + for (i = 0; i < RARRAY_LEN(ary); i++) { + rb_yield(rb_ary_new3(1, RARRAY_AREF(ary, i))); + } } else { - VALUE ary0 = ary_make_shared_copy(ary); /* private defensive copy of ary */ - volatile VALUE t0; - long *stack = ALLOCV_N(long, t0, n+1); + VALUE ary0 = ary_make_shared_copy(ary); /* private defensive copy of ary */ + volatile VALUE t0; + long *stack = ALLOCV_N(long, t0, n+1); - RBASIC_CLEAR_CLASS(ary0); - combinate0(len, n, stack, ary0); - ALLOCV_END(t0); - RBASIC_SET_CLASS_RAW(ary0, rb_cArray); + RBASIC_CLEAR_CLASS(ary0); + combinate0(len, n, stack, ary0); + ALLOCV_END(t0); + RBASIC_SET_CLASS_RAW(ary0, rb_cArray); } return ary; } @@ -6885,19 +7325,19 @@ rpermute0(const long n, const long r, long *const p, const VALUE values) p[index] = i; for (;;) { - if (++index < r-1) { - p[index] = i = 0; - continue; - } - for (i = 0; i < n; ++i) { - p[index] = i; - if (!yield_indexed_values(values, r, p)) { - rb_raise(rb_eRuntimeError, "repeated permute reentered"); - } - } - do { - if (index <= 0) return; - } while ((i = ++p[--index]) >= n); + if (++index < r-1) { + p[index] = i = 0; + continue; + } + for (i = 0; i < n; ++i) { + p[index] = i; + if (!yield_indexed_values(values, r, p)) { + rb_raise(rb_eRuntimeError, "repeated permute reentered"); + } + } + do { + if (index <= 0) return; + } while ((i = ++p[--index]) >= n); } } @@ -6908,10 +7348,10 @@ rb_ary_repeated_permutation_size(VALUE ary, VALUE args, VALUE eobj) long k = NUM2LONG(RARRAY_AREF(args, 0)); if (k < 0) { - return LONG2FIX(0); + return LONG2FIX(0); } if (n <= 0) { - return LONG2FIX(!k); + return LONG2FIX(!k); } return rb_int_positive_pow(n, (unsigned long)k); } @@ -6930,16 +7370,22 @@ rb_ary_repeated_permutation_size(VALUE ary, VALUE args, VALUE eobj) * The number of permutations is <tt>self.size**n</tt>. * * +n+ = 1: + * * a = [0, 1, 2] * a.repeated_permutation(1) {|permutation| p permutation } + * * Output: + * * [0] * [1] * [2] * * +n+ = 2: + * * a.repeated_permutation(2) {|permutation| p permutation } + * * Output: + * * [0, 0] * [0, 1] * [0, 2] @@ -6953,14 +7399,17 @@ rb_ary_repeated_permutation_size(VALUE ary, VALUE args, VALUE eobj) * If +n+ is zero, calls the block once with an empty \Array. * * If +n+ is negative, does not call the block: + * * a.repeated_permutation(-1) {|permutation| fail 'Cannot happen' } * * Returns a new \Enumerator if no block given: + * * a = [0, 1, 2] * a.repeated_permutation(2) # => #<Enumerator: [0, 1, 2]:permutation(2)> * * Using Enumerators, it's convenient to show the permutations and counts * for some values of +n+: + * * e = a.repeated_permutation(0) * e.size # => 1 * e.to_a # => [[]] @@ -6970,6 +7419,7 @@ rb_ary_repeated_permutation_size(VALUE ary, VALUE args, VALUE eobj) * e = a.repeated_permutation(2) * e.size # => 9 * e.to_a # => [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]] + * */ static VALUE rb_ary_repeated_permutation(VALUE ary, VALUE num) @@ -6981,25 +7431,25 @@ rb_ary_repeated_permutation(VALUE ary, VALUE num) r = NUM2LONG(num); /* Permutation size from argument */ if (r < 0) { - /* no permutations: yield nothing */ + /* no permutations: yield nothing */ } else if (r == 0) { /* exactly one permutation: the zero-length array */ - rb_yield(rb_ary_new2(0)); + rb_yield(rb_ary_new2(0)); } else if (r == 1) { /* this is a special, easy case */ - for (i = 0; i < RARRAY_LEN(ary); i++) { - rb_yield(rb_ary_new3(1, RARRAY_AREF(ary, i))); - } + for (i = 0; i < RARRAY_LEN(ary); i++) { + rb_yield(rb_ary_new3(1, RARRAY_AREF(ary, i))); + } } else { /* this is the general case */ - volatile VALUE t0; - long *p = ALLOCV_N(long, t0, r); - VALUE ary0 = ary_make_shared_copy(ary); /* private defensive copy of ary */ - RBASIC_CLEAR_CLASS(ary0); + volatile VALUE t0; + long *p = ALLOCV_N(long, t0, r); + VALUE ary0 = ary_make_shared_copy(ary); /* private defensive copy of ary */ + RBASIC_CLEAR_CLASS(ary0); - rpermute0(n, r, p, ary0); /* compute and yield repeated permutations */ - ALLOCV_END(t0); - RBASIC_SET_CLASS_RAW(ary0, rb_cArray); + rpermute0(n, r, p, ary0); /* compute and yield repeated permutations */ + ALLOCV_END(t0); + RBASIC_SET_CLASS_RAW(ary0, rb_cArray); } return ary; } @@ -7011,19 +7461,19 @@ rcombinate0(const long n, const long r, long *const p, const long rest, const VA p[index] = i; for (;;) { - if (++index < r-1) { - p[index] = i; - continue; - } - for (; i < n; ++i) { - p[index] = i; - if (!yield_indexed_values(values, r, p)) { - rb_raise(rb_eRuntimeError, "repeated combination reentered"); - } - } - do { - if (index <= 0) return; - } while ((i = ++p[--index]) >= n); + if (++index < r-1) { + p[index] = i; + continue; + } + for (; i < n; ++i) { + p[index] = i; + if (!yield_indexed_values(values, r, p)) { + rb_raise(rb_eRuntimeError, "repeated combination reentered"); + } + } + do { + if (index <= 0) return; + } while ((i = ++p[--index]) >= n); } } @@ -7033,7 +7483,7 @@ rb_ary_repeated_combination_size(VALUE ary, VALUE args, VALUE eobj) long n = RARRAY_LEN(ary); long k = NUM2LONG(RARRAY_AREF(args, 0)); if (k == 0) { - return LONG2FIX(1); + return LONG2FIX(1); } return binomial_coefficient(k, n + k - 1); } @@ -7052,16 +7502,22 @@ rb_ary_repeated_combination_size(VALUE ary, VALUE args, VALUE eobj) * The number of combinations is <tt>(n+1)(n+2)/2</tt>. * * +n+ = 1: + * * a = [0, 1, 2] * a.repeated_combination(1) {|combination| p combination } + * * Output: + * * [0] * [1] * [2] * * +n+ = 2: + * * a.repeated_combination(2) {|combination| p combination } + * * Output: + * * [0, 0] * [0, 1] * [0, 2] @@ -7072,14 +7528,17 @@ rb_ary_repeated_combination_size(VALUE ary, VALUE args, VALUE eobj) * If +n+ is zero, calls the block once with an empty \Array. * * If +n+ is negative, does not call the block: + * * a.repeated_combination(-1) {|combination| fail 'Cannot happen' } * * Returns a new \Enumerator if no block given: + * * a = [0, 1, 2] * a.repeated_combination(2) # => #<Enumerator: [0, 1, 2]:combination(2)> * * Using Enumerators, it's convenient to show the combinations and counts * for some values of +n+: + * * e = a.repeated_combination(0) * e.size # => 1 * e.to_a # => [[]] @@ -7089,6 +7548,7 @@ rb_ary_repeated_combination_size(VALUE ary, VALUE args, VALUE eobj) * e = a.repeated_combination(2) * e.size # => 6 * e.to_a # => [[0, 0], [0, 1], [0, 2], [1, 1], [1, 2], [2, 2]] + * */ static VALUE @@ -7100,28 +7560,28 @@ rb_ary_repeated_combination(VALUE ary, VALUE num) RETURN_SIZED_ENUMERATOR(ary, 1, &num, rb_ary_repeated_combination_size); /* Return enumerator if no block */ len = RARRAY_LEN(ary); if (n < 0) { - /* yield nothing */ + /* yield nothing */ } else if (n == 0) { - rb_yield(rb_ary_new2(0)); + rb_yield(rb_ary_new2(0)); } else if (n == 1) { - for (i = 0; i < RARRAY_LEN(ary); i++) { - rb_yield(rb_ary_new3(1, RARRAY_AREF(ary, i))); - } + for (i = 0; i < RARRAY_LEN(ary); i++) { + rb_yield(rb_ary_new3(1, RARRAY_AREF(ary, i))); + } } else if (len == 0) { - /* yield nothing */ + /* yield nothing */ } else { - volatile VALUE t0; - long *p = ALLOCV_N(long, t0, n); - VALUE ary0 = ary_make_shared_copy(ary); /* private defensive copy of ary */ - RBASIC_CLEAR_CLASS(ary0); + volatile VALUE t0; + long *p = ALLOCV_N(long, t0, n); + VALUE ary0 = ary_make_shared_copy(ary); /* private defensive copy of ary */ + RBASIC_CLEAR_CLASS(ary0); - rcombinate0(len, n, p, n, ary0); /* compute and yield repeated combinations */ - ALLOCV_END(t0); - RBASIC_SET_CLASS_RAW(ary0, rb_cArray); + rcombinate0(len, n, p, n, ary0); /* compute and yield repeated combinations */ + ALLOCV_END(t0); + RBASIC_SET_CLASS_RAW(ary0, rb_cArray); } return ary; } @@ -7132,12 +7592,14 @@ rb_ary_repeated_combination(VALUE ary, VALUE num) * array.product(*other_arrays) {|combination| ... } -> self * * Computes and returns or yields all combinations of elements from all the Arrays, - * including both +self+ and +other_arrays+. + * including both +self+ and +other_arrays+: + * * - The number of combinations is the product of the sizes of all the arrays, * including both +self+ and +other_arrays+. * - The order of the returned combinations is indeterminate. * * When no block is given, returns the combinations as an \Array of Arrays: + * * a = [0, 1, 2] * a1 = [3, 4] * a2 = [5, 6] @@ -7152,11 +7614,15 @@ rb_ary_repeated_combination(VALUE ary, VALUE num) * * If no argument is given, returns an \Array of 1-element Arrays, * each containing an element of +self+: + * * a.product # => [[0], [1], [2]] * * When a block is given, yields each combination as an \Array; returns +self+: + * * a.product(a1) {|combination| p combination } + * * Output: + * * [0, 3] * [0, 4] * [1, 3] @@ -7165,21 +7631,26 @@ rb_ary_repeated_combination(VALUE ary, VALUE num) * [2, 4] * * If any argument is an empty \Array, does not call the block: + * * a.product(a1, a2, []) {|combination| fail 'Cannot happen' } * * If no argument is given, yields each element of +self+ as a 1-element \Array: + * * a.product {|combination| p combination } + * * Output: + * * [0] * [1] * [2] + * */ static VALUE rb_ary_product(int argc, VALUE *argv, VALUE ary) { int n = argc+1; /* How many arrays we're operating on */ - volatile VALUE t0 = tmpary(n); + volatile VALUE t0 = rb_ary_hidden_new(n); volatile VALUE t1 = Qundef; VALUE *arrays = RARRAY_PTR(t0); /* The arrays we're computing the product of */ int *counters = ALLOCV_N(int, t1, n); /* The current position in each one */ @@ -7200,64 +7671,64 @@ rb_ary_product(int argc, VALUE *argv, VALUE ary) /* Otherwise, allocate and fill in an array of results */ if (rb_block_given_p()) { - /* Make defensive copies of arrays; exit if any is empty */ - for (i = 0; i < n; i++) { - if (RARRAY_LEN(arrays[i]) == 0) goto done; - arrays[i] = ary_make_shared_copy(arrays[i]); - } + /* Make defensive copies of arrays; exit if any is empty */ + for (i = 0; i < n; i++) { + if (RARRAY_LEN(arrays[i]) == 0) goto done; + arrays[i] = ary_make_shared_copy(arrays[i]); + } } else { - /* Compute the length of the result array; return [] if any is empty */ - for (i = 0; i < n; i++) { - long k = RARRAY_LEN(arrays[i]); - if (k == 0) { - result = rb_ary_new2(0); - goto done; - } + /* Compute the length of the result array; return [] if any is empty */ + for (i = 0; i < n; i++) { + long k = RARRAY_LEN(arrays[i]); + if (k == 0) { + result = rb_ary_new2(0); + goto done; + } if (MUL_OVERFLOW_LONG_P(resultlen, k)) - rb_raise(rb_eRangeError, "too big to product"); - resultlen *= k; - } - result = rb_ary_new2(resultlen); + rb_raise(rb_eRangeError, "too big to product"); + resultlen *= k; + } + result = rb_ary_new2(resultlen); } for (;;) { - int m; - /* fill in one subarray */ - VALUE subarray = rb_ary_new2(n); - for (j = 0; j < n; j++) { - rb_ary_push(subarray, rb_ary_entry(arrays[j], counters[j])); - } - - /* put it on the result array */ - if (NIL_P(result)) { - FL_SET(t0, FL_USER5); - rb_yield(subarray); - if (! FL_TEST(t0, FL_USER5)) { - rb_raise(rb_eRuntimeError, "product reentered"); - } - else { - FL_UNSET(t0, FL_USER5); - } - } - else { - rb_ary_push(result, subarray); - } - - /* - * Increment the last counter. If it overflows, reset to 0 - * and increment the one before it. - */ - m = n-1; - counters[m]++; - while (counters[m] == RARRAY_LEN(arrays[m])) { - counters[m] = 0; - /* If the first counter overflows, we are done */ - if (--m < 0) goto done; - counters[m]++; - } + int m; + /* fill in one subarray */ + VALUE subarray = rb_ary_new2(n); + for (j = 0; j < n; j++) { + rb_ary_push(subarray, rb_ary_entry(arrays[j], counters[j])); + } + + /* put it on the result array */ + if (NIL_P(result)) { + FL_SET(t0, RARRAY_SHARED_ROOT_FLAG); + rb_yield(subarray); + if (!FL_TEST(t0, RARRAY_SHARED_ROOT_FLAG)) { + rb_raise(rb_eRuntimeError, "product reentered"); + } + else { + FL_UNSET(t0, RARRAY_SHARED_ROOT_FLAG); + } + } + else { + rb_ary_push(result, subarray); + } + + /* + * Increment the last counter. If it overflows, reset to 0 + * and increment the one before it. + */ + m = n-1; + counters[m]++; + while (counters[m] == RARRAY_LEN(arrays[m])) { + counters[m] = 0; + /* If the first counter overflows, we are done */ + if (--m < 0) goto done; + counters[m]++; + } } + done: - tmpary_discard(t0); ALLOCV_END(t1); return NIL_P(result) ? ary : result; @@ -7272,11 +7743,13 @@ done: * does not modify +self+. * * Examples: + * * a = [0, 1, 2, 3, 4, 5] * a.take(1) # => [0] * a.take(2) # => [0, 1] * a.take(50) # => [0, 1, 2, 3, 4, 5] * a # => [0, 1, 2, 3, 4, 5] + * */ static VALUE @@ -7284,7 +7757,7 @@ rb_ary_take(VALUE obj, VALUE n) { long len = NUM2LONG(n); if (len < 0) { - rb_raise(rb_eArgError, "attempt to take negative size"); + rb_raise(rb_eArgError, "attempt to take negative size"); } return rb_ary_subseq(obj, 0, len); } @@ -7299,14 +7772,17 @@ rb_ary_take(VALUE obj, VALUE n) * * With a block given, calls the block with each successive element of +self+; * stops if the block returns +false+ or +nil+; - * returns a new Array containing those elements for which the block returned a truthy value: + * returns a new \Array containing those elements for which the block returned a truthy value: + * * a = [0, 1, 2, 3, 4, 5] * a.take_while {|element| element < 3 } # => [0, 1, 2] * a.take_while {|element| true } # => [0, 1, 2, 3, 4, 5] * a # => [0, 1, 2, 3, 4, 5] * * With no block given, returns a new \Enumerator: + * * [0, 1].take_while # => #<Enumerator: [0, 1]:take_while> + * */ static VALUE @@ -7316,7 +7792,7 @@ rb_ary_take_while(VALUE ary) RETURN_ENUMERATOR(ary, 0, 0); for (i = 0; i < RARRAY_LEN(ary); i++) { - if (!RTEST(rb_yield(RARRAY_AREF(ary, i)))) break; + if (!RTEST(rb_yield(RARRAY_AREF(ary, i)))) break; } return rb_ary_take(ary, LONG2FIX(i)); } @@ -7330,10 +7806,12 @@ rb_ary_take_while(VALUE ary) * does not modify +self+. * * Examples: + * * a = [0, 1, 2, 3, 4, 5] * a.drop(0) # => [0, 1, 2, 3, 4, 5] * a.drop(1) # => [1, 2, 3, 4, 5] * a.drop(2) # => [2, 3, 4, 5] + * */ static VALUE @@ -7342,7 +7820,7 @@ rb_ary_drop(VALUE ary, VALUE n) VALUE result; long pos = NUM2LONG(n); if (pos < 0) { - rb_raise(rb_eArgError, "attempt to drop negative size"); + rb_raise(rb_eArgError, "attempt to drop negative size"); } result = rb_ary_subseq(ary, pos, RARRAY_LEN(ary)); @@ -7360,12 +7838,15 @@ rb_ary_drop(VALUE ary, VALUE n) * * With a block given, calls the block with each successive element of +self+; * stops if the block returns +false+ or +nil+; - * returns a new Array _omitting_ those elements for which the block returned a truthy value: + * returns a new \Array _omitting_ those elements for which the block returned a truthy value: + * * a = [0, 1, 2, 3, 4, 5] * a.drop_while {|element| element < 3 } # => [3, 4, 5] * * With no block given, returns a new \Enumerator: + * * [0, 1].drop_while # => # => #<Enumerator: [0, 1]:drop_while> + * */ static VALUE @@ -7375,7 +7856,7 @@ rb_ary_drop_while(VALUE ary) RETURN_ENUMERATOR(ary, 0, 0); for (i = 0; i < RARRAY_LEN(ary); i++) { - if (!RTEST(rb_yield(RARRAY_AREF(ary, i)))) break; + if (!RTEST(rb_yield(RARRAY_AREF(ary, i)))) break; } return rb_ary_drop(ary, LONG2FIX(i)); } @@ -7390,17 +7871,20 @@ rb_ary_drop_while(VALUE ary) * * With no block given and no argument, returns +true+ if +self+ has any truthy element, * +false+ otherwise: + * * [nil, 0, false].any? # => true * [nil, false].any? # => false * [].any? # => false * * With a block given and no argument, calls the block with each element in +self+; * returns +true+ if the block returns any truthy value, +false+ otherwise: + * * [0, 1, 2].any? {|element| element > 1 } # => true * [0, 1, 2].any? {|element| element > 2 } # => false * * If argument +obj+ is given, returns +true+ if +obj+.<tt>===</tt> any element, * +false+ otherwise: + * * ['food', 'drink'].any?(/foo/) # => true * ['food', 'drink'].any?(/bar/) # => false * [].any?(/foo/) # => false @@ -7421,9 +7905,9 @@ rb_ary_any_p(int argc, VALUE *argv, VALUE ary) if (rb_block_given_p()) { rb_warn("given block not used"); } - for (i = 0; i < RARRAY_LEN(ary); ++i) { - if (RTEST(rb_funcall(argv[0], idEqq, 1, RARRAY_AREF(ary, i)))) return Qtrue; - } + for (i = 0; i < RARRAY_LEN(ary); ++i) { + if (RTEST(rb_funcall(argv[0], idEqq, 1, RARRAY_AREF(ary, i)))) return Qtrue; + } } else if (!rb_block_given_p()) { for (i = 0; i < len; ++i) { @@ -7431,9 +7915,9 @@ rb_ary_any_p(int argc, VALUE *argv, VALUE ary) } } else { - for (i = 0; i < RARRAY_LEN(ary); ++i) { - if (RTEST(rb_yield(RARRAY_AREF(ary, i)))) return Qtrue; - } + for (i = 0; i < RARRAY_LEN(ary); ++i) { + if (RTEST(rb_yield(RARRAY_AREF(ary, i)))) return Qtrue; + } } return Qfalse; } @@ -7448,16 +7932,19 @@ rb_ary_any_p(int argc, VALUE *argv, VALUE ary) * * With no block given and no argument, returns +true+ if +self+ contains only truthy elements, * +false+ otherwise: + * * [0, 1, :foo].all? # => true * [0, nil, 2].all? # => false * [].all? # => true * * With a block given and no argument, calls the block with each element in +self+; * returns +true+ if the block returns only truthy values, +false+ otherwise: + * * [0, 1, 2].all? { |element| element < 3 } # => true * [0, 1, 2].all? { |element| element < 2 } # => false * * If argument +obj+ is given, returns +true+ if <tt>obj.===</tt> every element, +false+ otherwise: + * * ['food', 'fool', 'foot'].all?(/foo/) # => true * ['food', 'drink'].all?(/bar/) # => false * [].all?(/foo/) # => true @@ -7505,16 +7992,19 @@ rb_ary_all_p(int argc, VALUE *argv, VALUE ary) * * With no block given and no argument, returns +true+ if +self+ has no truthy elements, * +false+ otherwise: + * * [nil, false].none? # => true * [nil, 0, false].none? # => false * [].none? # => true * * With a block given and no argument, calls the block with each element in +self+; * returns +true+ if the block returns no truthy value, +false+ otherwise: + * * [0, 1, 2].none? {|element| element > 3 } # => true * [0, 1, 2].none? {|element| element > 1 } # => false * * If argument +obj+ is given, returns +true+ if <tt>obj.===</tt> no element, +false+ otherwise: + * * ['food', 'drink'].none?(/bar/) # => true * ['food', 'drink'].none?(/foo/) # => false * [].none?(/foo/) # => true @@ -7562,6 +8052,7 @@ rb_ary_none_p(int argc, VALUE *argv, VALUE ary) * * With no block given and no argument, returns +true+ if +self+ has exactly one truthy element, * +false+ otherwise: + * * [nil, 0].one? # => true * [0, 0].one? # => false * [nil, nil].one? # => false @@ -7569,12 +8060,14 @@ rb_ary_none_p(int argc, VALUE *argv, VALUE ary) * * With a block given and no argument, calls the block with each element in +self+; * returns +true+ if the block a truthy value for exactly one element, +false+ otherwise: + * * [0, 1, 2].one? {|element| element > 0 } # => false * [0, 1, 2].one? {|element| element > 1 } # => true * [0, 1, 2].one? {|element| element > 2 } # => false * * If argument +obj+ is given, returns +true+ if <tt>obj.===</tt> exactly one element, * +false+ otherwise: + * * [0, 1, 2].one?(0) # => true * [0, 0, 1].one?(0) # => false * [1, 1, 2].one?(0) # => false @@ -7633,11 +8126,13 @@ rb_ary_one_p(int argc, VALUE *argv, VALUE ary) * See {Dig Methods}[rdoc-ref:dig_methods.rdoc]. * * Examples: + * * a = [:foo, [:bar, :baz, [:bat, :bam]]] * a.dig(1) # => [:bar, :baz, [:bat, :bam]] * a.dig(1, 2) # => [:bat, :bam] * a.dig(1, 2, 0) # => :bat * a.dig(1, 2, 3) # => nil + * */ static VALUE @@ -7655,7 +8150,7 @@ finish_exact_sum(long n, VALUE r, VALUE v, int z) { if (n != 0) v = rb_fix_plus(LONG2FIX(n), v); - if (r != Qundef) { + if (!UNDEF_P(r)) { v = rb_rational_plus(r, v); } else if (!n && z) { @@ -7670,31 +8165,38 @@ finish_exact_sum(long n, VALUE r, VALUE v, int z) * array.sum(init = 0) {|element| ... } -> object * * When no block is given, returns the object equivalent to: + * * sum = init * array.each {|element| sum += element } * sum + * * For example, <tt>[e1, e2, e3].sum</tt> returns <tt>init + e1 + e2 + e3</tt>. * * Examples: + * * a = [0, 1, 2, 3] * a.sum # => 6 * a.sum(100) # => 106 * * The elements need not be numeric, but must be <tt>+</tt>-compatible * with each other and with +init+: + * * a = ['abc', 'def', 'ghi'] * a.sum('jkl') # => "jklabcdefghi" * * When a block is given, it is called with each element * and the block's return value (instead of the element itself) is used as the addend: + * * a = ['zero', 1, :two] * s = a.sum('Coerced and concatenated: ') {|element| element.to_s } * s # => "Coerced and concatenated: zero1two" * * Notes: + * * - Array#join and Array#flatten may be faster than Array#sum * for an \Array of Strings or an \Array of Arrays. * - Array#sum method may not respect method redefinition of "+" methods such as Integer#+. + * */ static VALUE @@ -7727,7 +8229,7 @@ rb_ary_sum(int argc, VALUE *argv, VALUE ary) else if (RB_BIGNUM_TYPE_P(e)) v = rb_big_plus(e, v); else if (RB_TYPE_P(e, T_RATIONAL)) { - if (r == Qundef) + if (UNDEF_P(r)) r = e; else r = rb_rational_plus(r, e); @@ -7814,82 +8316,128 @@ rb_ary_deconstruct(VALUE ary) } /* - * An \Array is an ordered, integer-indexed collection of objects, - * called _elements_. Any object may be an \Array element. + * An \Array is an ordered, integer-indexed collection of objects, called _elements_. + * Any object (even another array) may be an array element, + * and an array can contain objects of different types. * * == \Array Indexes * * \Array indexing starts at 0, as in C or Java. * * A positive index is an offset from the first element: + * * - Index 0 indicates the first element. * - Index 1 indicates the second element. * - ... * * A negative index is an offset, backwards, from the end of the array: + * * - Index -1 indicates the last element. * - Index -2 indicates the next-to-last element. * - ... * - * A non-negative index is <i>in range</i> if it is smaller than + * A non-negative index is <i>in range</i> if and only if it is smaller than * the size of the array. For a 3-element array: + * * - Indexes 0 through 2 are in range. * - Index 3 is out of range. * - * A negative index is <i>in range</i> if its absolute value is + * A negative index is <i>in range</i> if and only if its absolute value is * not larger than the size of the array. For a 3-element array: + * * - Indexes -1 through -3 are in range. * - Index -4 is out of range. * - * == Creating Arrays - * - * You can create an \Array object explicitly with: - * - * - An {array literal}[doc/syntax/literals_rdoc.html#label-Array+Literals]. - * - * You can convert certain objects to Arrays with: - * - * - \Method {Array}[Kernel.html#method-i-Array]. - * - * An \Array can contain different types of objects. For - * example, the array below contains an Integer, a String and a Float: + * Although the effective index into an array is always an integer, + * some methods (both within and outside of class \Array) + * accept one or more non-integer arguments that are + * {integer-convertible objects}[rdoc-ref:implicit_conversion.rdoc@Integer-Convertible+Objects]. * - * ary = [1, "two", 3.0] #=> [1, "two", 3.0] * - * An array can also be created by calling Array.new with zero, one - * (the initial size of the Array) or two arguments (the initial size and a - * default object). - * - * ary = Array.new #=> [] - * Array.new(3) #=> [nil, nil, nil] - * Array.new(3, true) #=> [true, true, true] - * - * Note that the second argument populates the array with references to the - * same object. Therefore, it is only recommended in cases when you need to - * instantiate arrays with natively immutable objects such as Symbols, - * numbers, true or false. - * - * To create an array with separate objects a block can be passed instead. - * This method is safe to use with mutable objects such as hashes, strings or - * other arrays: - * - * Array.new(4) {Hash.new} #=> [{}, {}, {}, {}] - * Array.new(4) {|i| i.to_s } #=> ["0", "1", "2", "3"] - * - * This is also a quick way to build up multi-dimensional arrays: - * - * empty_table = Array.new(3) {Array.new(3)} - * #=> [[nil, nil, nil], [nil, nil, nil], [nil, nil, nil]] + * == Creating Arrays * - * An array can also be created by using the Array() method, provided by - * Kernel, which tries to call #to_ary, then #to_a on its argument. + * You can create an \Array object explicitly with: * - * Array({:a => "a", :b => "b"}) #=> [[:a, "a"], [:b, "b"]] + * - An {array literal}[rdoc-ref:literals.rdoc@Array+Literals]: + * + * [1, 'one', :one, [2, 'two', :two]] + * + * - A {%w or %W: string-array Literal}[rdoc-ref:literals.rdoc@25w+and+-25W-3A+String-Array+Literals]: + * + * %w[foo bar baz] # => ["foo", "bar", "baz"] + * %w[1 % *] # => ["1", "%", "*"] + * + * - A {%i pr %I: symbol-array Literal}[rdoc-ref:literals.rdoc@25i+and+-25I-3A+Symbol-Array+Literals]: + * + * %i[foo bar baz] # => [:foo, :bar, :baz] + * %i[1 % *] # => [:"1", :%, :*] + * + * - \Method Kernel#Array: + * + * Array(["a", "b"]) # => ["a", "b"] + * Array(1..5) # => [1, 2, 3, 4, 5] + * Array(key: :value) # => [[:key, :value]] + * Array(nil) # => [] + * Array(1) # => [1] + * Array({:a => "a", :b => "b"}) # => [[:a, "a"], [:b, "b"]] + * + * - \Method Array.new: + * + * Array.new # => [] + * Array.new(3) # => [nil, nil, nil] + * Array.new(4) {Hash.new} # => [{}, {}, {}, {}] + * Array.new(3, true) # => [true, true, true] + * + * Note that the last example above populates the array + * with references to the same object. + * This is recommended only in cases where that object is a natively immutable object + * such as a symbol, a numeric, +nil+, +true+, or +false+. + * + * Another way to create an array with various objects, using a block; + * this usage is safe for mutable objects such as hashes, strings or + * other arrays: + * + * Array.new(4) {|i| i.to_s } # => ["0", "1", "2", "3"] + * + * Here is a way to create a multi-dimensional array: + * + * Array.new(3) {Array.new(3)} + * # => [[nil, nil, nil], [nil, nil, nil], [nil, nil, nil]] + * + * A number of Ruby methods, both in the core and in the standard library, + * provide instance method +to_a+, which converts an object to an array. + * + * - ARGF#to_a + * - Array#to_a + * - Enumerable#to_a + * - Hash#to_a + * - MatchData#to_a + * - NilClass#to_a + * - OptionParser#to_a + * - Range#to_a + * - Set#to_a + * - Struct#to_a + * - Time#to_a + * - Benchmark::Tms#to_a + * - CSV::Table#to_a + * - Enumerator::Lazy#to_a + * - Gem::List#to_a + * - Gem::NameTuple#to_a + * - Gem::Platform#to_a + * - Gem::RequestSet::Lockfile::Tokenizer#to_a + * - Gem::SourceList#to_a + * - OpenSSL::X509::Extension#to_a + * - OpenSSL::X509::Name#to_a + * - Racc::ISet#to_a + * - Rinda::RingFinger#to_a + * - Ripper::Lexer::Elem#to_a + * - RubyVM::InstructionSequence#to_a + * - YAML::DBM#to_a * * == Example Usage * * In addition to the methods it mixes in through the Enumerable module, the - * Array class has proprietary methods for accessing, searching and otherwise + * \Array class has proprietary methods for accessing, searching and otherwise * manipulating arrays. * * Some of the more common ones are illustrated below. @@ -7937,7 +8485,7 @@ rb_ary_deconstruct(VALUE ary) * * arr.drop(3) #=> [4, 5, 6] * - * == Obtaining Information about an Array + * == Obtaining Information about an \Array * * Arrays keep track of their own length at all times. To query an array * about the number of elements it contains, use #length, #count or #size. @@ -7975,7 +8523,7 @@ rb_ary_deconstruct(VALUE ary) * arr.insert(3, 'orange', 'pear', 'grapefruit') * #=> [0, 1, 2, "orange", "pear", "grapefruit", "apple", 3, 4, 5, 6] * - * == Removing Items from an Array + * == Removing Items from an \Array * * The method #pop removes the last element in an array and returns it: * @@ -8017,9 +8565,9 @@ rb_ary_deconstruct(VALUE ary) * * == Iterating over Arrays * - * Like all classes that include the Enumerable module, Array has an each + * Like all classes that include the Enumerable module, \Array has an each * method, which defines what elements should be iterated over and how. In - * case of Array's #each, all elements in the Array instance are yielded to + * case of Array's #each, all elements in the \Array instance are yielded to * the supplied block in sequence. * * Note that this operation leaves the array unchanged. @@ -8045,7 +8593,8 @@ rb_ary_deconstruct(VALUE ary) * arr.map! {|a| a**2} #=> [1, 4, 9, 16, 25] * arr #=> [1, 4, 9, 16, 25] * - * == Selecting Items from an Array + * + * == Selecting Items from an \Array * * Elements can be selected from an array according to criteria defined in a * block. The selection can happen in a destructive or a non-destructive @@ -8080,181 +8629,184 @@ rb_ary_deconstruct(VALUE ary) * * First, what's elsewhere. \Class \Array: * - * - Inherits from {class Object}[Object.html#class-Object-label-What-27s+Here]. - * - Includes {module Enumerable}[Enumerable.html#module-Enumerable-label-What-27s+Here], + * - Inherits from {class Object}[rdoc-ref:Object@What-27s+Here]. + * - Includes {module Enumerable}[rdoc-ref:Enumerable@What-27s+Here], * which provides dozens of additional methods. * * Here, class \Array provides methods that are useful for: * - * - {Creating an Array}[#class-Array-label-Methods+for+Creating+an+Array] - * - {Querying}[#class-Array-label-Methods+for+Querying] - * - {Comparing}[#class-Array-label-Methods+for+Comparing] - * - {Fetching}[#class-Array-label-Methods+for+Fetching] - * - {Assigning}[#class-Array-label-Methods+for+Assigning] - * - {Deleting}[#class-Array-label-Methods+for+Deleting] - * - {Combining}[#class-Array-label-Methods+for+Combining] - * - {Iterating}[#class-Array-label-Methods+for+Iterating] - * - {Converting}[#class-Array-label-Methods+for+Converting] - * - {And more....}[#class-Array-label-Other+Methods] + * - {Creating an Array}[rdoc-ref:Array@Methods+for+Creating+an+Array] + * - {Querying}[rdoc-ref:Array@Methods+for+Querying] + * - {Comparing}[rdoc-ref:Array@Methods+for+Comparing] + * - {Fetching}[rdoc-ref:Array@Methods+for+Fetching] + * - {Assigning}[rdoc-ref:Array@Methods+for+Assigning] + * - {Deleting}[rdoc-ref:Array@Methods+for+Deleting] + * - {Combining}[rdoc-ref:Array@Methods+for+Combining] + * - {Iterating}[rdoc-ref:Array@Methods+for+Iterating] + * - {Converting}[rdoc-ref:Array@Methods+for+Converting] + * - {And more....}[rdoc-ref:Array@Other+Methods] * - * === Methods for Creating an Array + * === Methods for Creating an \Array * - * ::[]:: Returns a new array populated with given objects. - * ::new:: Returns a new array. - * ::try_convert:: Returns a new array created from a given object. + * - ::[]: Returns a new array populated with given objects. + * - ::new: Returns a new array. + * - ::try_convert: Returns a new array created from a given object. * * === Methods for Querying * - * #length, #size:: Returns the count of elements. - * #include?:: Returns whether any element <tt>==</tt> a given object. - * #empty?:: Returns whether there are no elements. - * #all?:: Returns whether all elements meet a given criterion. - * #any?:: Returns whether any element meets a given criterion. - * #none?:: Returns whether no element <tt>==</tt> a given object. - * #one?:: Returns whether exactly one element <tt>==</tt> a given object. - * #count:: Returns the count of elements that meet a given criterion. - * #find_index, #index:: Returns the index of the first element that meets a given criterion. - * #rindex:: Returns the index of the last element that meets a given criterion. - * #hash:: Returns the integer hash code. + * - #length, #size: Returns the count of elements. + * - #include?: Returns whether any element <tt>==</tt> a given object. + * - #empty?: Returns whether there are no elements. + * - #all?: Returns whether all elements meet a given criterion. + * - #any?: Returns whether any element meets a given criterion. + * - #none?: Returns whether no element <tt>==</tt> a given object. + * - #one?: Returns whether exactly one element <tt>==</tt> a given object. + * - #count: Returns the count of elements that meet a given criterion. + * - #find_index, #index: Returns the index of the first element that meets a given criterion. + * - #rindex: Returns the index of the last element that meets a given criterion. + * - #hash: Returns the integer hash code. * * === Methods for Comparing - * {#<=>}[#method-i-3C-3D-3E]:: Returns -1, 0, or 1 - * as +self+ is less than, equal to, or greater than a given object. - * {#==}[#method-i-3D-3D]:: Returns whether each element in +self+ is <tt>==</tt> to the - * corresponding element in a given object. - * #eql?:: Returns whether each element in +self+ is <tt>eql?</tt> to the corresponding - * element in a given object. + * + * - #<=>: Returns -1, 0, or 1 * as +self+ is less than, equal to, or + * greater than a given object. + * - #==: Returns whether each element in +self+ is <tt>==</tt> to the corresponding element + * in a given object. + * - #eql?: Returns whether each element in +self+ is <tt>eql?</tt> to the corresponding + * element in a given object. * === Methods for Fetching * * These methods do not modify +self+. * - * #[]:: Returns one or more elements. - * #fetch:: Returns the element at a given offset. - * #first:: Returns one or more leading elements. - * #last:: Returns one or more trailing elements. - * #max:: Returns one or more maximum-valued elements, - * as determined by <tt><=></tt> or a given block. - * #max:: Returns one or more minimum-valued elements, - * as determined by <tt><=></tt> or a given block. - * #minmax:: Returns the minimum-valued and maximum-valued elements, - * as determined by <tt><=></tt> or a given block. - * #assoc:: Returns the first element that is an array - * whose first element <tt>==</tt> a given object. - * #rassoc:: Returns the first element that is an array - * whose second element <tt>==</tt> a given object. - * #at:: Returns the element at a given offset. - * #values_at:: Returns the elements at given offsets. - * #dig:: Returns the object in nested objects - * that is specified by a given index and additional arguments. - * #drop:: Returns trailing elements as determined by a given index. - * #take:: Returns leading elements as determined by a given index. - * #drop_while:: Returns trailing elements as determined by a given block. - * #take_while:: Returns leading elements as determined by a given block. - * #slice:: Returns consecutive elements as determined by a given argument. - * #sort:: Returns all elements in an order determined by <tt><=></tt> or a given block. - * #reverse:: Returns all elements in reverse order. - * #compact:: Returns an array containing all non-+nil+ elements. - * #select, #filter:: Returns an array containing elements selected by a given block. - * #uniq:: Returns an array containing non-duplicate elements. - * #rotate:: Returns all elements with some rotated from one end to the other. - * #bsearch:: Returns an element selected via a binary search - * as determined by a given block. - * #bsearch_index:: Returns the index of an element selected via a binary search - * as determined by a given block. - * #sample:: Returns one or more random elements. - * #shuffle:: Returns elements in a random order. + * - #[]: Returns one or more elements. + * - #fetch: Returns the element at a given offset. + * - #first: Returns one or more leading elements. + * - #last: Returns one or more trailing elements. + * - #max: Returns one or more maximum-valued elements, + * as determined by <tt><=></tt> or a given block. + * - #min: Returns one or more minimum-valued elements, + * as determined by <tt><=></tt> or a given block. + * - #minmax: Returns the minimum-valued and maximum-valued elements, + * as determined by <tt><=></tt> or a given block. + * - #assoc: Returns the first element that is an array + * whose first element <tt>==</tt> a given object. + * - #rassoc: Returns the first element that is an array + * whose second element <tt>==</tt> a given object. + * - #at: Returns the element at a given offset. + * - #values_at: Returns the elements at given offsets. + * - #dig: Returns the object in nested objects + * that is specified by a given index and additional arguments. + * - #drop: Returns trailing elements as determined by a given index. + * - #take: Returns leading elements as determined by a given index. + * - #drop_while: Returns trailing elements as determined by a given block. + * - #take_while: Returns leading elements as determined by a given block. + * - #slice: Returns consecutive elements as determined by a given argument. + * - #sort: Returns all elements in an order determined by <tt><=></tt> or a given block. + * - #reverse: Returns all elements in reverse order. + * - #compact: Returns an array containing all non-+nil+ elements. + * - #select, #filter: Returns an array containing elements selected by a given block. + * - #uniq: Returns an array containing non-duplicate elements. + * - #rotate: Returns all elements with some rotated from one end to the other. + * - #bsearch: Returns an element selected via a binary search + * as determined by a given block. + * - #bsearch_index: Returns the index of an element selected via a binary search + * as determined by a given block. + * - #sample: Returns one or more random elements. + * - #shuffle: Returns elements in a random order. * * === Methods for Assigning * * These methods add, replace, or reorder elements in +self+. * - * #[]=:: Assigns specified elements with a given object. - * #push, #append, #<<:: Appends trailing elements. - * #unshift, #prepend:: Prepends leading elements. - * #insert:: Inserts given objects at a given offset; does not replace elements. - * #concat:: Appends all elements from given arrays. - * #fill:: Replaces specified elements with specified objects. - * #replace:: Replaces the content of +self+ with the content of a given array. - * #reverse!:: Replaces +self+ with its elements reversed. - * #rotate!:: Replaces +self+ with its elements rotated. - * #shuffle!:: Replaces +self+ with its elements in random order. - * #sort!:: Replaces +self+ with its elements sorted, - * as determined by <tt><=></tt> or a given block. - * #sort_by!:: Replaces +self+ with its elements sorted, as determined by a given block. + * - #[]=: Assigns specified elements with a given object. + * - #push, #append, #<<: Appends trailing elements. + * - #unshift, #prepend: Prepends leading elements. + * - #insert: Inserts given objects at a given offset; does not replace elements. + * - #concat: Appends all elements from given arrays. + * - #fill: Replaces specified elements with specified objects. + * - #replace: Replaces the content of +self+ with the content of a given array. + * - #reverse!: Replaces +self+ with its elements reversed. + * - #rotate!: Replaces +self+ with its elements rotated. + * - #shuffle!: Replaces +self+ with its elements in random order. + * - #sort!: Replaces +self+ with its elements sorted, + * as determined by <tt><=></tt> or a given block. + * - #sort_by!: Replaces +self+ with its elements sorted, as determined by a given block. * * === Methods for Deleting * * Each of these methods removes elements from +self+: * - * #pop:: Removes and returns the last element. - * #shift:: Removes and returns the first element. - * #compact!:: Removes all non-+nil+ elements. - * #delete:: Removes elements equal to a given object. - * #delete_at:: Removes the element at a given offset. - * #delete_if:: Removes elements specified by a given block. - * #keep_if:: Removes elements not specified by a given block. - * #reject!:: Removes elements specified by a given block. - * #select!, #filter!:: Removes elements not specified by a given block. - * #slice!:: Removes and returns a sequence of elements. - * #uniq!:: Removes duplicates. + * - #pop: Removes and returns the last element. + * - #shift: Removes and returns the first element. + * - #compact!: Removes all +nil+ elements. + * - #delete: Removes elements equal to a given object. + * - #delete_at: Removes the element at a given offset. + * - #delete_if: Removes elements specified by a given block. + * - #keep_if: Removes elements not specified by a given block. + * - #reject!: Removes elements specified by a given block. + * - #select!, #filter!: Removes elements not specified by a given block. + * - #slice!: Removes and returns a sequence of elements. + * - #uniq!: Removes duplicates. * * === Methods for Combining * - * {#&}[#method-i-26]:: Returns an array containing elements found both in +self+ and a given array. - * #intersection:: Returns an array containing elements found both in +self+ - * and in each given array. - * #+:: Returns an array containing all elements of +self+ followed by all elements of a given array. - * #-:: Returns an array containiing all elements of +self+ that are not found in a given array. - * {#|}[#method-i-7C]:: Returns an array containing all elements of +self+ and all elements of a given array, - * duplicates removed. - * #union:: Returns an array containing all elements of +self+ and all elements of given arrays, - * duplicates removed. - * #difference:: Returns an array containing all elements of +self+ that are not found - * in any of the given arrays.. - * #product:: Returns or yields all combinations of elements from +self+ and given arrays. + * - #&: Returns an array containing elements found both in +self+ and a given array. + * - #intersection: Returns an array containing elements found both in +self+ + * and in each given array. + * - #+: Returns an array containing all elements of +self+ followed by all elements of a given array. + * - #-: Returns an array containing all elements of +self+ that are not found in a given array. + * - #|: Returns an array containing all elements of +self+ and all elements of a given array, + * duplicates removed. + * - #union: Returns an array containing all elements of +self+ and all elements of given arrays, + * duplicates removed. + * - #difference: Returns an array containing all elements of +self+ that are not found + * in any of the given arrays.. + * - #product: Returns or yields all combinations of elements from +self+ and given arrays. * * === Methods for Iterating * - * #each:: Passes each element to a given block. - * #reverse_each:: Passes each element, in reverse order, to a given block. - * #each_index:: Passes each element index to a given block. - * #cycle:: Calls a given block with each element, then does so again, - * for a specified number of times, or forever. - * #combination:: Calls a given block with combinations of elements of +self+; - * a combination does not use the same element more than once. - * #permutation:: Calls a given block with permutations of elements of +self+; - * a permutation does not use the same element more than once. - * #repeated_combination:: Calls a given block with combinations of elements of +self+; - * a combination may use the same element more than once. - * #repeated_permutation:: Calls a given block with permutations of elements of +self+; - * a permutation may use the same element more than once. + * - #each: Passes each element to a given block. + * - #reverse_each: Passes each element, in reverse order, to a given block. + * - #each_index: Passes each element index to a given block. + * - #cycle: Calls a given block with each element, then does so again, + * for a specified number of times, or forever. + * - #combination: Calls a given block with combinations of elements of +self+; + * a combination does not use the same element more than once. + * - #permutation: Calls a given block with permutations of elements of +self+; + * a permutation does not use the same element more than once. + * - #repeated_combination: Calls a given block with combinations of elements of +self+; + * a combination may use the same element more than once. + * - #repeated_permutation: Calls a given block with permutations of elements of +self+; + * a permutation may use the same element more than once. * * === Methods for Converting * - * #map, #collect:: Returns an array containing the block return-value for each element. - * #map!, #collect!:: Replaces each element with a block return-value. - * #flatten:: Returns an array that is a recursive flattening of +self+. - * #flatten!:: Replaces each nested array in +self+ with the elements from that array. - * #inspect, #to_s:: Returns a new String containing the elements. - * #join:: Returns a newsString containing the elements joined by the field separator. - * #to_a:: Returns +self+ or a new array containing all elements. - * #to_ary:: Returns +self+. - * #to_h:: Returns a new hash formed from the elements. - * #transpose:: Transposes +self+, which must be an array of arrays. - * #zip:: Returns a new array of arrays containing +self+ and given arrays; - * follow the link for details. + * - #map, #collect: Returns an array containing the block return-value for each element. + * - #map!, #collect!: Replaces each element with a block return-value. + * - #flatten: Returns an array that is a recursive flattening of +self+. + * - #flatten!: Replaces each nested array in +self+ with the elements from that array. + * - #inspect, #to_s: Returns a new String containing the elements. + * - #join: Returns a newsString containing the elements joined by the field separator. + * - #to_a: Returns +self+ or a new array containing all elements. + * - #to_ary: Returns +self+. + * - #to_h: Returns a new hash formed from the elements. + * - #transpose: Transposes +self+, which must be an array of arrays. + * - #zip: Returns a new array of arrays containing +self+ and given arrays; + * follow the link for details. * * === Other Methods * - * #*:: Returns one of the following: - * - With integer argument +n+, a new array that is the concatenation - * of +n+ copies of +self+. - * - With string argument +field_separator+, a new string that is equivalent to - * <tt>join(field_separator)</tt>. - * #abbrev:: Returns a hash of unambiguous abbreviations for elements. - * #pack:: Packs the elements into a binary sequence. - * #sum:: Returns a sum of elements according to either <tt>+</tt> or a given block. + * - #*: Returns one of the following: + * + * - With integer argument +n+, a new array that is the concatenation + * of +n+ copies of +self+. + * - With string argument +field_separator+, a new string that is equivalent to + * <tt>join(field_separator)</tt>. + * + * - #abbrev: Returns a hash of unambiguous abbreviations for elements. + * - #pack: Packs the elements into a binary sequence. + * - #sum: Returns a sum of elements according to either <tt>+</tt> or a given block. */ void @@ -8264,6 +8816,7 @@ Init_Array(void) rb_include_module(rb_cArray, rb_mEnumerable); rb_define_alloc_func(rb_cArray, empty_ary_alloc); + rb_define_singleton_method(rb_cArray, "new", rb_ary_s_new, -1); rb_define_singleton_method(rb_cArray, "[]", rb_ary_s_create, -1); rb_define_singleton_method(rb_cArray, "try_convert", rb_ary_s_try_convert, 1); rb_define_method(rb_cArray, "initialize", rb_ary_initialize, -1); @@ -64,8 +64,8 @@ ast_new_internal(rb_ast_t *ast, const NODE *node) return obj; } -static VALUE rb_ast_parse_str(VALUE str, VALUE keep_script_lines); -static VALUE rb_ast_parse_file(VALUE path, VALUE keep_script_lines); +static VALUE rb_ast_parse_str(VALUE str, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens); +static VALUE rb_ast_parse_file(VALUE path, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens); static VALUE ast_parse_new(void) @@ -85,31 +85,33 @@ ast_parse_done(rb_ast_t *ast) } static VALUE -ast_s_parse(rb_execution_context_t *ec, VALUE module, VALUE str, VALUE keep_script_lines) +ast_s_parse(rb_execution_context_t *ec, VALUE module, VALUE str, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { - return rb_ast_parse_str(str, keep_script_lines); + return rb_ast_parse_str(str, keep_script_lines, error_tolerant, keep_tokens); } static VALUE -rb_ast_parse_str(VALUE str, VALUE keep_script_lines) +rb_ast_parse_str(VALUE str, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { rb_ast_t *ast = 0; StringValue(str); VALUE vparser = ast_parse_new(); if (RTEST(keep_script_lines)) rb_parser_keep_script_lines(vparser); + if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser); + if (RTEST(keep_tokens)) rb_parser_keep_tokens(vparser); ast = rb_parser_compile_string_path(vparser, Qnil, str, 1); return ast_parse_done(ast); } static VALUE -ast_s_parse_file(rb_execution_context_t *ec, VALUE module, VALUE path, VALUE keep_script_lines) +ast_s_parse_file(rb_execution_context_t *ec, VALUE module, VALUE path, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { - return rb_ast_parse_file(path, keep_script_lines); + return rb_ast_parse_file(path, keep_script_lines, error_tolerant, keep_tokens); } static VALUE -rb_ast_parse_file(VALUE path, VALUE keep_script_lines) +rb_ast_parse_file(VALUE path, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { VALUE f; rb_ast_t *ast = 0; @@ -120,6 +122,8 @@ rb_ast_parse_file(VALUE path, VALUE keep_script_lines) rb_funcall(f, rb_intern("set_encoding"), 2, rb_enc_from_encoding(enc), rb_str_new_cstr("-")); VALUE vparser = ast_parse_new(); if (RTEST(keep_script_lines)) rb_parser_keep_script_lines(vparser); + if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser); + if (RTEST(keep_tokens)) rb_parser_keep_tokens(vparser); ast = rb_parser_compile_file_path(vparser, Qnil, f, 1); rb_io_close(f); return ast_parse_done(ast); @@ -139,13 +143,15 @@ lex_array(VALUE array, int index) } static VALUE -rb_ast_parse_array(VALUE array, VALUE keep_script_lines) +rb_ast_parse_array(VALUE array, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { rb_ast_t *ast = 0; array = rb_check_array_type(array); VALUE vparser = ast_parse_new(); if (RTEST(keep_script_lines)) rb_parser_keep_script_lines(vparser); + if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser); + if (RTEST(keep_tokens)) rb_parser_keep_tokens(vparser); ast = rb_parser_compile_generic(vparser, lex_array, Qnil, array, 1); return ast_parse_done(ast); } @@ -193,7 +199,24 @@ script_lines(VALUE path) } static VALUE -ast_s_of(rb_execution_context_t *ec, VALUE module, VALUE body, VALUE keep_script_lines) +node_id_for_backtrace_location(rb_execution_context_t *ec, VALUE module, VALUE location) +{ + int node_id; + + if (!rb_frame_info_p(location)) { + rb_raise(rb_eTypeError, "Thread::Backtrace::Location object expected"); + } + + node_id = rb_get_node_id_from_frame_info(location); + if (node_id == -1) { + return Qnil; + } + + return INT2NUM(node_id); +} + +static VALUE +ast_s_of(rb_execution_context_t *ec, VALUE module, VALUE body, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { VALUE node, lines = Qnil; const rb_iseq_t *iseq; @@ -215,14 +238,14 @@ ast_s_of(rb_execution_context_t *ec, VALUE module, VALUE body, VALUE keep_script iseq = rb_method_iseq(body); } if (iseq) { - node_id = iseq->body->location.node_id; + node_id = ISEQ_BODY(iseq)->location.node_id; } } if (!iseq) { return Qnil; } - lines = iseq->body->variable.script_lines; + lines = ISEQ_BODY(iseq)->variable.script_lines; VALUE path = rb_iseq_path(iseq); int e_option = RSTRING_LEN(path) == 2 && memcmp(RSTRING_PTR(path), "-e", 2) == 0; @@ -232,13 +255,13 @@ ast_s_of(rb_execution_context_t *ec, VALUE module, VALUE body, VALUE keep_script } if (!NIL_P(lines) || !NIL_P(lines = script_lines(path))) { - node = rb_ast_parse_array(lines, keep_script_lines); + node = rb_ast_parse_array(lines, keep_script_lines, error_tolerant, keep_tokens); } else if (e_option) { - node = rb_ast_parse_str(rb_e_script, keep_script_lines); + node = rb_ast_parse_str(rb_e_script, keep_script_lines, error_tolerant, keep_tokens); } else { - node = rb_ast_parse_file(path, keep_script_lines); + node = rb_ast_parse_file(path, keep_script_lines, error_tolerant, keep_tokens); } return node_find(node, node_id); @@ -645,6 +668,8 @@ node_children(rb_ast_t *ast, const NODE *node) NEW_CHILD(ast, node->nd_pkwargs), kwrest); } + case NODE_ERROR: + return rb_ary_new_from_node_args(ast, 0); case NODE_ARGS_AUX: case NODE_LAST: break; @@ -699,6 +724,15 @@ ast_node_last_column(rb_execution_context_t *ec, VALUE self) } static VALUE +ast_node_all_tokens(rb_execution_context_t *ec, VALUE self) +{ + struct ASTNodeData *data; + TypedData_Get_Struct(self, struct ASTNodeData, &rb_node_type, data); + + return rb_ast_tokens(data->ast); +} + +static VALUE ast_node_inspect(rb_execution_context_t *ec, VALUE self) { VALUE str; @@ -20,21 +20,47 @@ module RubyVM::AbstractSyntaxTree # call-seq: - # RubyVM::AbstractSyntaxTree.parse(string) -> RubyVM::AbstractSyntaxTree::Node + # RubyVM::AbstractSyntaxTree.parse(string, keep_script_lines: false, error_tolerant: false, keep_tokens: false) -> RubyVM::AbstractSyntaxTree::Node # # Parses the given _string_ into an abstract syntax tree, # returning the root node of that tree. # - # SyntaxError is raised if the given _string_ is invalid syntax. - # # RubyVM::AbstractSyntaxTree.parse("x = 1 + 2") # # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-1:9> - def self.parse string, keep_script_lines: false - Primitive.ast_s_parse string, keep_script_lines + # + # If <tt>keep_script_lines: true</tt> option is provided, the text of the parsed + # source is associated with nodes and is available via Node#script_lines. + # + # If <tt>keep_tokens: true</tt> option is provided, Node#tokens are populated. + # + # SyntaxError is raised if the given _string_ is invalid syntax. To overwrite this + # behavior, <tt>error_tolerant: true</tt> can be provided. In this case, the parser + # will produce a tree where expressions with syntax errors would be represented by + # Node with <tt>type=:ERROR</tt>. + # + # root = RubyVM::AbstractSyntaxTree.parse("x = 1; p(x; y=2") + # # <internal:ast>:33:in `parse': syntax error, unexpected ';', expecting ')' (SyntaxError) + # # x = 1; p(x; y=2 + # # ^ + # + # root = RubyVM::AbstractSyntaxTree.parse("x = 1; p(x; y=2", error_tolerant: true) + # # (SCOPE@1:0-1:15 + # # tbl: [:x, :y] + # # args: nil + # # body: (BLOCK@1:0-1:15 (LASGN@1:0-1:5 :x (LIT@1:4-1:5 1)) (ERROR@1:7-1:11) (LASGN@1:12-1:15 :y (LIT@1:14-1:15 2)))) + # root.children.last.children + # # [(LASGN@1:0-1:5 :x (LIT@1:4-1:5 1)), + # # (ERROR@1:7-1:11), + # # (LASGN@1:12-1:15 :y (LIT@1:14-1:15 2))] + # + # Note that parsing continues even after the errored expresion. + # + def self.parse string, keep_script_lines: false, error_tolerant: false, keep_tokens: false + Primitive.ast_s_parse string, keep_script_lines, error_tolerant, keep_tokens end # call-seq: - # RubyVM::AbstractSyntaxTree.parse_file(pathname) -> RubyVM::AbstractSyntaxTree::Node + # RubyVM::AbstractSyntaxTree.parse_file(pathname, keep_script_lines: false, error_tolerant: false, keep_tokens: false) -> RubyVM::AbstractSyntaxTree::Node # # Reads the file from _pathname_, then parses it like ::parse, # returning the root node of the abstract syntax tree. @@ -44,13 +70,15 @@ module RubyVM::AbstractSyntaxTree # # RubyVM::AbstractSyntaxTree.parse_file("my-app/app.rb") # # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-31:3> - def self.parse_file pathname, keep_script_lines: false - Primitive.ast_s_parse_file pathname, keep_script_lines + # + # See ::parse for explanation of keyword argument meaning and usage. + def self.parse_file pathname, keep_script_lines: false, error_tolerant: false, keep_tokens: false + Primitive.ast_s_parse_file pathname, keep_script_lines, error_tolerant, keep_tokens end # call-seq: - # RubyVM::AbstractSyntaxTree.of(proc) -> RubyVM::AbstractSyntaxTree::Node - # RubyVM::AbstractSyntaxTree.of(method) -> RubyVM::AbstractSyntaxTree::Node + # RubyVM::AbstractSyntaxTree.of(proc, keep_script_lines: false, error_tolerant: false, keep_tokens: false) -> RubyVM::AbstractSyntaxTree::Node + # RubyVM::AbstractSyntaxTree.of(method, keep_script_lines: false, error_tolerant: false, keep_tokens: false) -> RubyVM::AbstractSyntaxTree::Node # # Returns AST nodes of the given _proc_ or _method_. # @@ -63,8 +91,25 @@ module RubyVM::AbstractSyntaxTree # # RubyVM::AbstractSyntaxTree.of(method(:hello)) # # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-3:3> - def self.of body, keep_script_lines: false - Primitive.ast_s_of body, keep_script_lines + # + # See ::parse for explanation of keyword argument meaning and usage. + def self.of body, keep_script_lines: false, error_tolerant: false, keep_tokens: false + Primitive.ast_s_of body, keep_script_lines, error_tolerant, keep_tokens + end + + # call-seq: + # RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(backtrace_location) -> integer + # + # Returns the node id for the given backtrace location. + # + # begin + # raise + # rescue => e + # loc = e.backtrace_locations.first + # RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc) + # end # => 0 + def self.node_id_for_backtrace_location backtrace_location + Primitive.node_id_for_backtrace_location backtrace_location end # RubyVM::AbstractSyntaxTree::Node instances are created by parse methods in @@ -122,6 +167,47 @@ module RubyVM::AbstractSyntaxTree end # call-seq: + # node.tokens -> array + # + # Returns tokens corresponding to the location of the node. + # Returns +nil+ if +keep_tokens+ is not enabled when #parse method is called. + # + # root = RubyVM::AbstractSyntaxTree.parse("x = 1 + 2", keep_tokens: true) + # root.tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...] + # root.tokens.map{_1[2]}.join # => "x = 1 + 2" + # + # Token is an array of: + # + # - id + # - token type + # - source code text + # - location [ first_lineno, first_column, last_lineno, last_column ] + def tokens + return nil unless all_tokens + + all_tokens.each_with_object([]) do |token, a| + loc = token.last + if ([first_lineno, first_column] <=> [loc[0], loc[1]]) <= 0 && + ([last_lineno, last_column] <=> [loc[2], loc[3]]) >= 0 + a << token + end + end + end + + # call-seq: + # node.all_tokens -> array + # + # Returns all tokens for the input script regardless the receiver node. + # Returns +nil+ if +keep_tokens+ is not enabled when #parse method is called. + # + # root = RubyVM::AbstractSyntaxTree.parse("x = 1 + 2", keep_tokens: true) + # root.all_tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...] + # root.children[-1].all_tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...] + def all_tokens + Primitive.ast_node_all_tokens + end + + # call-seq: # node.children -> array # # Returns AST nodes under this one. Each kind of node diff --git a/basictest/test.rb b/basictest/test.rb index 52008b78db..95875b52a6 100755 --- a/basictest/test.rb +++ b/basictest/test.rb @@ -1960,6 +1960,8 @@ test_ok(p1.call == 5) test_ok(i7 == nil) end +# WASI doesn't support spawning a new process for now. +unless /wasi/ =~ RUBY_PLATFORM test_check "system" test_ok(`echo foobar` == "foobar\n") test_ok(`./miniruby -e 'print "foobar"'` == 'foobar') @@ -2010,6 +2012,7 @@ test_ok(done) File.unlink script_tmp or `/bin/rm -f "#{script_tmp}"` File.unlink "#{script_tmp}.bak" or `/bin/rm -f "#{script_tmp}.bak"` +end # not /wasi/ =~ RUBY_PLATFORM test_check "const" TEST1 = 1 diff --git a/benchmark/README.md b/benchmark/README.md index c222164be3..e11381cad9 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -28,16 +28,18 @@ See also: ```console Usage: benchmark-driver [options] RUBY|YAML... - -r, --runner TYPE Specify runner type: ips, time, memory, once (default: ips) - -o, --output TYPE Specify output type: compare, simple, markdown, record (default: compare) + -r, --runner TYPE Specify runner type: ips, time, memory, once, block (default: ips) + -o, --output TYPE Specify output type: compare, simple, markdown, record, all (default: compare) -e, --executables EXECS Ruby executables (e1::path1 arg1; e2::path2 arg2;...) --rbenv VERSIONS Ruby executables in rbenv (x.x.x arg1;y.y.y arg2;...) --repeat-count NUM Try benchmark NUM times and use the fastest result or the worst memory usage --repeat-result TYPE Yield "best", "average" or "worst" result with --repeat-count (default: best) + --alternate Alternate executables instead of running the same executable in a row with --repeat-count --bundler Install and use gems specified in Gemfile --filter REGEXP Filter out benchmarks with given regexp --run-duration SECONDS Warmup estimates loop_count to run for this duration (default: 3) - -v, --verbose Verbose mode. Multiple -v options increase visibility (max: 2) + --timeout SECONDS Timeout ruby command execution with timeout(1) + -v, --verbose Verbose mode. Multiple -v options increase visilibity (max: 2) ``` ## make benchmark diff --git a/benchmark/array_sort_int.yml b/benchmark/array_sort_int.yml new file mode 100644 index 0000000000..7b9027ebf7 --- /dev/null +++ b/benchmark/array_sort_int.yml @@ -0,0 +1,15 @@ +prelude: | + ary2 = 2.times.to_a.shuffle + ary10 = 10.times.to_a.shuffle + ary100 = 100.times.to_a.shuffle + ary1000 = 1000.times.to_a.shuffle + ary10000 = 10000.times.to_a.shuffle + +benchmark: + ary2.sort: ary2.sort + ary10.sort: ary10.sort + ary100.sort: ary100.sort + ary1000.sort: ary1000.sort + ary10000.sort: ary10000.sort + +loop_count: 10000 diff --git a/benchmark/buffer_each.yml b/benchmark/buffer_each.yml new file mode 100644 index 0000000000..417941104e --- /dev/null +++ b/benchmark/buffer_each.yml @@ -0,0 +1,27 @@ +prelude: | + # frozen_string_literal: true + Warning[:experimental] = false + string = "The quick brown fox jumped over the lazy dog." + array = string.bytes + buffer = IO::Buffer.for(string) +benchmark: + string.each_byte: | + upcased = String.new + string.each_byte do |byte| + upcased << (byte ^ 32) + end + array.each: | + upcased = String.new + array.each do |byte| + upcased << (byte ^ 32) + end + buffer.each: | + upcased = String.new + buffer.each(:U8) do |offset, byte| + upcased << (byte ^ 32) + end + buffer.each_byte: | + upcased = String.new + buffer.each_byte do |byte| + upcased << (byte ^ 32) + end diff --git a/benchmark/buffer_get.yml b/benchmark/buffer_get.yml index e375dcf85d..9e1f99d64e 100644 --- a/benchmark/buffer_get.yml +++ b/benchmark/buffer_get.yml @@ -1,9 +1,25 @@ +prelude: | + # frozen_string_literal: true + Warning[:experimental] = false + string = "The quick brown fox jumped over the lazy dog." + buffer = IO::Buffer.for(string) + format = [:U32, :U32, :U32, :U32] benchmark: - - name: buffer.get - prelude: buffer = IO::Buffer.new(32, IO::Buffer::MAPPED) - script: buffer.get(:U32, 0) - loop_count: 20000000 - - name: string.unpack - prelude: string = "\0" * 32 - script: string.unpack("C") - loop_count: 20000000 + string.unpack1: | + [ + string.unpack1("N"), + string.unpack1("N", offset: 4), + string.unpack1("N", offset: 8), + string.unpack1("N", offset: 12), + ] + buffer.get_value: | + [ + buffer.get_value(:U32, 0), + buffer.get_value(:U32, 4), + buffer.get_value(:U32, 8), + buffer.get_value(:U32, 12), + ] + buffer.get_values: | + buffer.get_values(format, 0) + string.unpack: | + string.unpack("NNNN") diff --git a/benchmark/cgi_escape_html.yml b/benchmark/cgi_escape_html.yml index af6abd08ac..655be9d7d8 100644 --- a/benchmark/cgi_escape_html.yml +++ b/benchmark/cgi_escape_html.yml @@ -1,32 +1,23 @@ -prelude: require 'cgi/escape' +prelude: | + # frozen_string_literal: true + require 'cgi/escape' benchmark: - - name: escape_html_blank - prelude: str = "" - script: CGI.escapeHTML(str) + - script: CGI.escapeHTML("") loop_count: 20000000 - - name: escape_html_short_none - prelude: str = "abcde" - script: CGI.escapeHTML(str) + - script: CGI.escapeHTML("abcde") loop_count: 20000000 - - name: escape_html_short_one - prelude: str = "abcd<" - script: CGI.escapeHTML(str) + - script: CGI.escapeHTML("abcd<") loop_count: 20000000 - - name: escape_html_short_all - prelude: str = "'&\"<>" - script: CGI.escapeHTML(str) + - script: CGI.escapeHTML("'&\"<>") loop_count: 5000000 - - name: escape_html_long_none - prelude: str = "abcde" * 300 - script: CGI.escapeHTML(str) + - prelude: long_no_escape = "abcde" * 300 + script: CGI.escapeHTML(long_no_escape) loop_count: 1000000 - - name: escape_html_long_all - prelude: str = "'&\"<>" * 10 - script: CGI.escapeHTML(str) + - prelude: long_all_escape = "'&\"<>" * 10 + script: CGI.escapeHTML(long_all_escape) loop_count: 1000000 - - name: escape_html_real - prelude: | # http://example.com/ - str = <<~HTML + - prelude: | # http://example.com/ + example_html = <<~HTML <body> <div> <h1>Example Domain</h1> @@ -36,5 +27,5 @@ benchmark: </div> </body> HTML - script: CGI.escapeHTML(str) + script: CGI.escapeHTML(example_html) loop_count: 1000000 diff --git a/benchmark/constant_invalidation.rb b/benchmark/constant_invalidation.rb new file mode 100644 index 0000000000..a95ec6f37e --- /dev/null +++ b/benchmark/constant_invalidation.rb @@ -0,0 +1,22 @@ +$VERBOSE = nil + +CONSTANT1 = 1 +CONSTANT2 = 1 +CONSTANT3 = 1 +CONSTANT4 = 1 +CONSTANT5 = 1 + +def constants + [CONSTANT1, CONSTANT2, CONSTANT3, CONSTANT4, CONSTANT5] +end + +500_000.times do + constants + + # With previous behavior, this would cause all of the constant caches + # associated with the constant lookups listed above to invalidate, meaning + # they would all have to be fetched again. With current behavior, it only + # invalidates when a name matches, so the following constant set shouldn't + # impact the constant lookups listed above. + INVALIDATE = true +end diff --git a/benchmark/enum_minmax.yml b/benchmark/enum_minmax.yml new file mode 100644 index 0000000000..9d01731abb --- /dev/null +++ b/benchmark/enum_minmax.yml @@ -0,0 +1,25 @@ +prelude: | + set2 = 2.times.to_a.shuffle.to_set + set10 = 10.times.to_a.shuffle.to_set + set100 = 100.times.to_a.shuffle.to_set + set1000 = 1000.times.to_a.shuffle.to_set + set10000 = 10000.times.to_a.shuffle.to_set + +benchmark: + set2.min: set2.min + set10.min: set10.min + set100.min: set100.min + set1000.min: set1000.min + set10000.min: set10000.min + set2.max: set2.max + set10.max: set10.max + set100.max: set100.max + set1000.max: set1000.max + set10000.max: set10000.max + set2.minmax: set2.minmax + set10.minmax: set10.minmax + set100.minmax: set100.minmax + set1000.minmax: set1000.minmax + set10000.minmax: set10000.minmax + +loop_count: 10000 diff --git a/benchmark/enum_sort.yml b/benchmark/enum_sort.yml new file mode 100644 index 0000000000..6f26e748c6 --- /dev/null +++ b/benchmark/enum_sort.yml @@ -0,0 +1,15 @@ +prelude: | + set2 = 2.times.to_a.shuffle.to_set + set10 = 10.times.to_a.shuffle.to_set + set100 = 100.times.to_a.shuffle.to_set + set1000 = 1000.times.to_a.shuffle.to_set + set10000 = 10000.times.to_a.shuffle.to_set + +benchmark: + set2.sort_by: set2.sort_by { 0 } + set10.sort_by: set10.sort_by { 0 } + set100.sort_by: set100.sort_by { 0 } + set1000.sort_by: set1000.sort_by { 0 } + set10000.sort_by: set10000.sort_by { 0 } + +loop_count: 10000 diff --git a/benchmark/erb_escape_html.yml b/benchmark/erb_escape_html.yml new file mode 100644 index 0000000000..ca28d756e7 --- /dev/null +++ b/benchmark/erb_escape_html.yml @@ -0,0 +1,31 @@ +prelude: | + # frozen_string_literal: true + require 'erb' +benchmark: + - script: ERB::Util.html_escape("") + loop_count: 20000000 + - script: ERB::Util.html_escape("abcde") + loop_count: 20000000 + - script: ERB::Util.html_escape("abcd<") + loop_count: 20000000 + - script: ERB::Util.html_escape("'&\"<>") + loop_count: 5000000 + - prelude: long_no_escape = "abcde" * 300 + script: ERB::Util.html_escape(long_no_escape) + loop_count: 1000000 + - prelude: long_all_escape = "'&\"<>" * 10 + script: ERB::Util.html_escape(long_all_escape) + loop_count: 1000000 + - prelude: | # http://example.com/ + example_html = <<~HTML + <body> + <div> + <h1>Example Domain</h1> + <p>This domain is established to be used for illustrative examples in documents. You may use this + domain in examples without prior coordination or asking for permission.</p> + <p><a href="http://www.iana.org/domains/example">More information...</a></p> + </div> + </body> + HTML + script: ERB::Util.html_escape(example_html) + loop_count: 1000000 diff --git a/benchmark/io_write.rb b/benchmark/io_write.rb new file mode 100644 index 0000000000..cdb409948b --- /dev/null +++ b/benchmark/io_write.rb @@ -0,0 +1,22 @@ +#!/usr/bin/env ruby + +require 'benchmark' + +i, o = IO.pipe +o.sync = true + +DOT = ".".freeze + +chunks = 100_000.times.collect{DOT} + +thread = Thread.new do + while i.read(1024) + end +end + +100.times do + o.write(*chunks) +end + +o.close +thread.join diff --git a/benchmark/lib/benchmark_driver/runner/mjit.rb b/benchmark/lib/benchmark_driver/runner/mjit.rb index 1d4693e8be..3a58a620de 100644 --- a/benchmark/lib/benchmark_driver/runner/mjit.rb +++ b/benchmark/lib/benchmark_driver/runner/mjit.rb @@ -16,7 +16,7 @@ class BenchmarkDriver::Runner::Mjit < BenchmarkDriver::Runner::Ips job.prelude = "#{job.prelude}\n#{<<~EOS}" if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? __bmdv_ruby_i = 0 - while __bmdv_ruby_i < 10000 # jit_min_calls + while __bmdv_ruby_i < 10000 # MJIT call threshold #{job.script} __bmdv_ruby_i += 1 end diff --git a/benchmark/lib/benchmark_driver/runner/mjit_exec.rb b/benchmark/lib/benchmark_driver/runner/mjit_exec.rb deleted file mode 100644 index eac3dfba84..0000000000 --- a/benchmark/lib/benchmark_driver/runner/mjit_exec.rb +++ /dev/null @@ -1,237 +0,0 @@ -require 'benchmark_driver/struct' -require 'benchmark_driver/metric' -require 'erb' - -# A special runner dedicated for measuring mjit_exec overhead. -class BenchmarkDriver::Runner::MjitExec - METRIC = BenchmarkDriver::Metric.new(name: 'Iteration per second', unit: 'i/s') - - # JobParser returns this, `BenchmarkDriver::Runner.runner_for` searches "*::Job" - Job = ::BenchmarkDriver::Struct.new( - :name, # @param [String] name - This is mandatory for all runner - :metrics, # @param [Array<BenchmarkDriver::Metric>] - :num_methods, # @param [Integer] num_methods - The number of methods to be defined - :loop_count, # @param [Integer] loop_count - :from_jit, # @param [TrueClass,FalseClass] from_jit - Whether the mjit_exec() is from JIT or not - :to_jit, # @param [TrueClass,FalseClass] to_jit - Whether the mjit_exec() is to JIT or not - ) - # Dynamically fetched and used by `BenchmarkDriver::JobParser.parse` - class << JobParser = Module.new - # @param [Array,String] num_methods - # @param [Integer] loop_count - # @param [TrueClass,FalseClass] from_jit - # @param [TrueClass,FalseClass] to_jit - def parse(num_methods:, loop_count:, from_jit:, to_jit:) - if num_methods.is_a?(String) - num_methods = eval(num_methods) - end - - num_methods.map do |num| - if num_methods.size > 1 - suffix = "[#{'%4d' % num}]" - else - suffix = "_#{num}" - end - Job.new( - name: "mjit_exec_#{from_jit ? 'JT' : 'VM'}2#{to_jit ? 'JT' : 'VM'}#{suffix}", - metrics: [METRIC], - num_methods: num, - loop_count: loop_count, - from_jit: from_jit, - to_jit: to_jit, - ) - end - end - end - - # @param [BenchmarkDriver::Config::RunnerConfig] config - # @param [BenchmarkDriver::Output] output - # @param [BenchmarkDriver::Context] contexts - def initialize(config:, output:, contexts:) - @config = config - @output = output - @contexts = contexts - end - - # This method is dynamically called by `BenchmarkDriver::JobRunner.run` - # @param [Array<BenchmarkDriver::Runner::Peak::Job>] jobs - def run(jobs) - @output.with_benchmark do - jobs.each do |job| - @output.with_job(name: job.name) do - @contexts.each do |context| - result = BenchmarkDriver::Repeater.with_repeat(config: @config, larger_better: true, rest_on_average: :average) do - run_benchmark(job, context: context) - end - value, duration = result.value - @output.with_context(name: context.name, executable: context.executable, gems: context.gems, prelude: context.prelude) do - @output.report(values: { METRIC => value }, duration: duration, loop_count: job.loop_count) - end - end - end - end - end - end - - private - - # @param [BenchmarkDriver::Runner::Ips::Job] job - loop_count is not nil - # @param [BenchmarkDriver::Context] context - # @return [BenchmarkDriver::Metrics] - def run_benchmark(job, context:) - if job.from_jit - if job.to_jit - benchmark = BenchmarkJT2JT.new(num_methods: job.num_methods, loop_count: job.loop_count) - else - raise NotImplementedError, "JT2VM is not implemented yet" - end - else - if job.to_jit - benchmark = BenchmarkVM2JT.new(num_methods: job.num_methods, loop_count: job.loop_count) - else - benchmark = BenchmarkVM2VM.new(num_methods: job.num_methods, loop_count: job.loop_count) - end - end - - duration = Tempfile.open(['benchmark_driver-result', '.txt']) do |f| - with_script(benchmark.render(result: f.path)) do |path| - opt = [] - if context.executable.command.any? { |c| c.start_with?('--jit') } - opt << '--jit-min-calls=2' - end - IO.popen([*context.executable.command, '--disable-gems', *opt, path], &:read) - if $?.success? - Float(f.read) - else - BenchmarkDriver::Result::ERROR - end - end - end - - [job.loop_count.to_f / duration, duration] - end - - def with_script(script) - if @config.verbose >= 2 - sep = '-' * 30 - $stdout.puts "\n\n#{sep}[Script begin]#{sep}\n#{script}#{sep}[Script end]#{sep}\n\n" - end - - Tempfile.open(['benchmark_driver-', '.rb']) do |f| - f.puts script - f.close - return yield(f.path) - end - end - - # @param [Integer] num_methods - # @param [Integer] loop_count - BenchmarkVM2VM = ::BenchmarkDriver::Struct.new(:num_methods, :loop_count) do - # @param [String] result - A file to write result - def render(result:) - ERB.new(<<~EOS, trim_mode: '%').result(binding) - % num_methods.times do |i| - def a<%= i %> - nil - end - % end - RubyVM::MJIT.pause if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? - - def vm - t = Process.clock_gettime(Process::CLOCK_MONOTONIC) - i = 0 - while i < <%= loop_count / 1000 %> - % 1000.times do |i| - a<%= i % num_methods %> - % end - i += 1 - end - % (loop_count % 1000).times do |i| - a<%= i % num_methods %> - % end - Process.clock_gettime(Process::CLOCK_MONOTONIC) - t - end - - vm # warmup call cache - File.write(<%= result.dump %>, vm) - EOS - end - end - private_constant :BenchmarkVM2VM - - # @param [Integer] num_methods - # @param [Integer] loop_count - BenchmarkVM2JT = ::BenchmarkDriver::Struct.new(:num_methods, :loop_count) do - # @param [String] result - A file to write result - def render(result:) - ERB.new(<<~EOS, trim_mode: '%').result(binding) - % num_methods.times do |i| - def a<%= i %> - nil - end - a<%= i %> - a<%= i %> # --jit-min-calls=2 - % end - RubyVM::MJIT.pause if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? - - def vm - t = Process.clock_gettime(Process::CLOCK_MONOTONIC) - i = 0 - while i < <%= loop_count / 1000 %> - % 1000.times do |i| - a<%= i % num_methods %> - % end - i += 1 - end - % (loop_count % 1000).times do |i| - a<%= i % num_methods %> - % end - Process.clock_gettime(Process::CLOCK_MONOTONIC) - t - end - - vm # warmup call cache - File.write(<%= result.dump %>, vm) - EOS - end - end - private_constant :BenchmarkVM2JT - - # @param [Integer] num_methods - # @param [Integer] loop_count - BenchmarkJT2JT = ::BenchmarkDriver::Struct.new(:num_methods, :loop_count) do - # @param [String] result - A file to write result - def render(result:) - ERB.new(<<~EOS, trim_mode: '%').result(binding) - % num_methods.times do |i| - def a<%= i %> - nil - end - % end - - # You may need to: - # * Increase `JIT_ISEQ_SIZE_THRESHOLD` to 10000000 in mjit.h - # * Always return false in `inlinable_iseq_p()` of mjit_compile.c - def jit - t = Process.clock_gettime(Process::CLOCK_MONOTONIC) - i = 0 - while i < <%= loop_count / 1000 %> - % 1000.times do |i| - a<%= i % num_methods %> - % end - i += 1 - end - % (loop_count % 1000).times do |i| - a<%= i % num_methods %> - % end - Process.clock_gettime(Process::CLOCK_MONOTONIC) - t - end - - jit - jit - RubyVM::MJIT.pause if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? - File.write(<%= result.dump %>, jit) - EOS - end - end - private_constant :BenchmarkJT2JT -end diff --git a/benchmark/marshal_dump_load_integer.yml b/benchmark/marshal_dump_load_integer.yml new file mode 100644 |
