diff options
7788 files changed, 1177084 insertions, 433076 deletions
diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 0000000000..05ff204541 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,134 @@ +--- +version: '{build}' +init: + - git config --global user.name git + - git config --global user.email svn-admin@ruby-lang.org + - git config --global core.autocrlf false + - git config --global core.eol lf + - git config --global advice.detachedHead 0 +shallow_clone: true +clone_depth: 10 +platform: + - x64 +skip_commits: + message: /\[DOC\]/ + files: + - doc/* + - '**/*.md' + - '**/*.rdoc' + - '**/.document' + - '**/*.[1-8]' + - '**/*.ronn' +environment: + ruby_version: "24-%Platform%" + 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 # 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: + only: + - build: vs + install: + - ver + - chcp + - 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%' + - SET ruby_path=C:\Ruby%ruby_version:-x86=% + - SET PATH=\usr\local\bin;%ruby_path%\bin;%PATH%;C:\msys64\mingw64\bin;C:\msys64\usr\bin + - ruby --version + - 'cl' + - echo> Makefile srcdir=. + - echo>> Makefile MSC_VER=0 + - echo>> Makefile RT=none + - echo>> Makefile RT_VER=0 + - echo>> Makefile BUILTIN_ENCOBJS=nul + - type win32\Makefile.sub >> Makefile + - nmake %mflags% up VCSUP="echo Update OK" + - nmake %mflags% extract-extlibs + - del Makefile + - mkdir \usr\local\bin + - mkdir \usr\local\include + - mkdir \usr\local\lib + - 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 + --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')" + - if not "%GEMS_FOR_TEST%" == "" \usr\bin\gem install --no-document %GEMS_FOR_TEST% + - \usr\bin\ruby -ropenssl -e "puts 'Build ' + OpenSSL::OPENSSL_VERSION, 'Runtime ' + OpenSSL::OPENSSL_LIBRARY_VERSION" + test_script: + - set /a JOBS=%NUMBER_OF_PROCESSORS% + - nmake -l "TESTOPTS=-v -q" btest + - nmake -l "TESTOPTS=-v -q" test-basic + - >- + 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=--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 + method: POST + url: + secure: CcFlJNDJ/a6to7u3Z4Fnz6dScEPNx7hTha2GkSRlV+1U6dqmxY/7uBcLXYb9gR3jfQk6w+2o/HrjNAyXMNGU/JOka3s2WRI4VKitzM+lQ08owvJIh0R7LxrGH0J2e81U # ruby-lang slack: ruby/simpler-alerts-bot + body: >- + {{^isPullRequest}} + { + "ci": "AppVeyor CI", + "env": "Visual Studio 2013", + "url": "{{buildUrl}}", + "commit": "{{commitId}}", + "branch": "{{branch}}" + } + {{/isPullRequest}} + on_build_success: false + on_build_failure: true + on_build_status_changed: false diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000000..8f4b96445c --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,39 @@ +;; Copyright (c) 2018 Urabe, Shyouhei. All rights reserved. +;; +;; This file is a part of the programming language Ruby. Permission is hereby +;; granted, to either redistribute and/or modify this file, provided that the +;; conditions mentioned in the file COPYING are met. Consult the file for +;; details. + +((nil . + ((indent-tabs-mode . nil) + (require-final-newline . t) + (tab-width . 8) + (show-trailing-whitespace . t) + (whitespace-line-column . 80))) ;; See also [Misc #12277] + + ;; (bat-mode . ((buffer-file-coding-system . utf-8-dos))) + + (ruby-mode . ((ruby-indent-level . 2))) + + (rdoc-mode . ((fill-column . 74))) + + (yaml-mode . ((yaml-indent-offset . 2))) + + (makefile-mode . ((indent-tabs-mode . t))) + + (c-mode . ((c-file-style . "ruby"))) + + (c++-mode . ((c-file-style . "ruby"))) + + (change-log-mode . + ((buffer-file-coding-system . us-ascii) + (indent-tabs-mode . t) + (change-log-indent-text . 2) + (add-log-time-format . (lambda (&optional x y) + (let* ((time (or x (current-time))) + (system-time-locale "C") + (diff (+ (cadr time) 32400)) + (lo (% diff 65536)) + (hi (+ (car time) (/ diff 65536)))) + (format-time-string "%a %b %e %H:%M:%S %Y" (list hi lo) t))))))) @@ -9,9 +9,28 @@ # prelude prelude.rb - rbconfig.rb +array.rb +ast.rb +dir.rb +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 @@ -19,14 +38,13 @@ lib ext # rdoc files -NEWS +NEWS.md README.md README.ja.md COPYING COPYING.ja -CONTRIBUTING.md LEGAL diff --git a/.editorconfig b/.editorconfig index cb2729fff9..4633a7acae 100644 --- a/.editorconfig +++ b/.editorconfig @@ -23,5 +23,8 @@ indent_size = 2 [{*[Mm]akefile*,*.mak,*.mk,depend}] indent_style = tab +[enc/*] +indent_size = 2 + [reg*.[ch]] indent_size = 2 @@ -136,7 +136,7 @@ define rp printf "%sT_ARRAY%s: len=%ld ", $color_type, $color_end, $len if ($flags & RUBY_FL_USER2) printf "(shared) shared=" - output/x ((struct RArray*)($arg0))->as.heap.aux.shared + output/x ((struct RArray*)($arg0))->as.heap.aux.shared_root printf " " else printf "(ownership) capa=%ld ", ((struct RArray*)($arg0))->as.heap.aux.capa @@ -156,12 +156,12 @@ define rp else if ($flags & RUBY_T_MASK) == RUBY_T_HASH printf "%sT_HASH%s: ", $color_type, $color_end, - if (((struct RHash *)($arg0))->basic->flags & RHASH_ST_TABLE_FLAG) + if (((struct RHash *)($arg0))->basic.flags & RHASH_ST_TABLE_FLAG) printf "st len=%ld ", ((struct RHash *)($arg0))->as.st->num_entries else printf "li len=%ld bound=%ld ", \ - ((((struct RHash *)($arg0))->basic->flags & RHASH_ARRAY_LEN_MASK) >> RHASH_ARRAY_LEN_SHIFT), \ - ((((struct RHash *)($arg0))->basic->flags & RHASH_ARRAY_BOUND_MASK) >> RHASH_ARRAY_BOUND_SHIFT) + ((((struct RHash *)($arg0))->basic.flags & RHASH_AR_TABLE_SIZE_MASK) >> RHASH_AR_TABLE_SIZE_SHIFT), \ + ((((struct RHash *)($arg0))->basic.flags & RHASH_AR_TABLE_BOUND_MASK) >> RHASH_AR_TABLE_BOUND_SHIFT) end print (struct RHash *)($arg0) else @@ -265,6 +265,10 @@ define rp printf "%sT_ZOMBIE%s: ", $color_type, $color_end print (struct RData *)($arg0) else + if ($flags & RUBY_T_MASK) == RUBY_T_MOVED + printf "%sT_MOVED%s: ", $color_type, $color_end + print *(struct RMoved *)$arg0 + else printf "%sunknown%s: ", $color_type, $color_end print (struct RBasic *)($arg0) end @@ -300,6 +304,7 @@ define rp end end end + end end document rp Print a Ruby's VALUE. @@ -539,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. @@ -974,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) @@ -1073,9 +1078,9 @@ define print_id else set $serial = (rb_id_serial_t)$id end - if $serial && $serial <= global_symbols.last_id + if $serial && $serial <= ruby_global_symbols.last_id set $idx = $serial / ID_ENTRY_UNIT - set $ids = (struct RArray *)global_symbols.ids + set $ids = (struct RArray *)ruby_global_symbols.ids set $flags = $ids->basic.flags if ($flags & RUBY_FL_USER1) set $idsptr = $ids->as.ary @@ -1097,11 +1102,11 @@ define print_id set $arylen = $ary->as.heap.len end set $result = $aryptr[($serial % ID_ENTRY_UNIT) * ID_ENTRY_SIZE + $t] - if $result != RUBY_Qnil + if $result != RUBY_Qnil print_string $result - else - echo undef - end + else + echo undef + end end end end @@ -1274,7 +1279,7 @@ document rb_count_objects Counts all objects grouped by type. end -# Details: https://bugs.ruby-lang.org/projects/ruby-trunk/wiki/MachineInstructionsTraceWithGDB +# Details: https://bugs.ruby-lang.org/projects/ruby-master/wiki/MachineInstructionsTraceWithGDB define trace_machine_instructions set logging on set height 0 @@ -1319,8 +1324,7 @@ define print_flags printf "RUBY_FL_PROMOTED0 : %s\n", ((struct RBasic*)($arg0))->flags & RUBY_FL_PROMOTED0 ? "1" : "0" printf "RUBY_FL_PROMOTED1 : %s\n", ((struct RBasic*)($arg0))->flags & RUBY_FL_PROMOTED1 ? "1" : "0" printf "RUBY_FL_FINALIZE : %s\n", ((struct RBasic*)($arg0))->flags & RUBY_FL_FINALIZE ? "1" : "0" - printf "RUBY_FL_TAINT : %s\n", ((struct RBasic*)($arg0))->flags & RUBY_FL_TAINT ? "1" : "0" - printf "RUBY_FL_UNTRUSTED : %s\n", ((struct RBasic*)($arg0))->flags & RUBY_FL_UNTRUSTED ? "1" : "0" + printf "RUBY_FL_SHAREABLE : %s\n", ((struct RBasic*)($arg0))->flags & RUBY_FL_SHAREABLE ? "1" : "0" printf "RUBY_FL_EXIVAR : %s\n", ((struct RBasic*)($arg0))->flags & RUBY_FL_EXIVAR ? "1" : "0" printf "RUBY_FL_FREEZE : %s\n", ((struct RBasic*)($arg0))->flags & RUBY_FL_FREEZE ? "1" : "0" 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/.gitattributes b/.gitattributes index 6ca2f89462..d0c2d266b4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,3 +4,5 @@ bin svn-properties=svn:ignore=ruby bin/* diff=ruby tool/update-deps diff=ruby tool/make-snapshot diff=ruby +tool/format-release diff=ruby +tool/leaked-globals diff=ruby diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000000..56baa29942 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,9 @@ +# Security Policy + +## Supported Versions + +See <https://www.ruby-lang.org/en/downloads/branches/>. + +## Reporting a Vulnerability + +See <https://www.ruby-lang.org/en/security/>. diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 0000000000..91f82b842b --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,3 @@ +name: "CodeQL config for the Ruby language" + +languages: cpp 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 new file mode 100644 index 0000000000..ebaafe3bf0 --- /dev/null +++ b/.github/workflows/baseruby.yml @@ -0,0 +1,80 @@ +name: BASERUBY Check + +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-22.04 + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + strategy: + matrix: + ruby: + - ruby-2.2 +# - ruby-2.3 +# - ruby-2.4 +# - ruby-2.5 +# - ruby-2.6 +# - ruby-2.7 + - ruby-3.0 + - ruby-3.1 + + steps: + - 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@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 libyaml-dev + - run: ./autogen.sh + - run: ./configure --disable-install-doc + - run: make common-srcs + - run: make incs + - run: make all + - run: make test + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 + with: + payload: | + { + "ci": "GitHub Actions", + "env": "${{ github.workflow }} / BASERUBY @ ${{ matrix.ruby }}", + "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/bundled_gems.yml b/.github/workflows/bundled_gems.yml new file mode 100644 index 0000000000..070c0fa1dd --- /dev/null +++ b/.github/workflows/bundled_gems.yml @@ -0,0 +1,166 @@ +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 + steps: + - name: git config + run: | + git config --global advice.detachedHead 0 + git config --global init.defaultBranch garbage + + - name: Set ENV + run: | + echo "GNUMAKEFLAGS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV + echo "TODAY=$(date +%F)" >> $GITHUB_ENV + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: .downloaded-cache + key: downloaded-cache-${{ github.sha }} + restore-keys: | + downloaded-cache + + - name: Download previous gems list + run: | + data=bundled_gems.json + mkdir -p .downloaded-cache + ln -s .downloaded-cache/$data . + curl -O -R -z ./$data https://stdgems.org/$data + + - name: Update bundled gems list + run: | + ruby -i~ tool/update-bundled_gems.rb gems/bundled_gems + + - name: Maintain updated gems list in NEWS + run: | + #!ruby + require 'json' + news = File.read("NEWS.md") + prev = news[/since the \*+(\d+\.\d+\.\d+)\*+/, 1] + prevs = [prev, prev.sub(/\.\d+\z/, '')] + %W[bundled].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 + changed, added = changed.partition {|g, _| last[g]} + 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} *#{$2}" + added.map {|g, v|"#{mark}#{g} #{v}\n"}.join("") + end or next unless added.empty? + File.write("NEWS.md", news) + end + shell: ruby {0} + + - name: Check diffs + id: diff + run: | + git add -- NEWS.md + git diff --no-ext-diff --ignore-submodules --quiet -- gems/bundled_gems + continue-on-error: true + + - name: Install libraries + run: | + set -x + sudo apt-get update -q || : + sudo apt-get install --no-install-recommends -q -y build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev bison autoconf ruby + if: ${{ steps.diff.outcome == 'failure' }} + + - name: Build + run: | + ./autogen.sh + ./configure -C --disable-install-doc + 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 + git add -- gems/bundled_gems + timeout-minutes: 30 + env: + RUBY_TESTOPTS: "-q --tty=no" + TEST_BUNDLED_GEMS_ALLOW_FAILURES: "" + if: ${{ steps.diff.outcome == 'failure' }} + + - name: Show diffs + id: show + run: | + git diff --cached --color --no-ext-diff --ignore-submodules --exit-code -- + continue-on-error: true + + - name: Commit + run: | + git pull --ff-only origin ${GITHUB_REF#refs/heads/} + message="Update bundled gems list at " + if [ ${{ steps.diff.outcome }} = success ]; then + git commit --message="${message}${GITHUB_SHA:0:30} [ci skip]" + else + git commit --message="${message}${TODAY}" + fi + 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.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 new file mode 100644 index 0000000000..79b2916feb --- /dev/null +++ b/.github/workflows/check_dependencies.yml @@ -0,0 +1,78 @@ +name: Check Dependencies +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-22.04] + fail-fast: true + runs-on: ${{ matrix.os }} + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + steps: + - name: Install libraries + run: | + set -x + sudo apt-get update -q || : + sudo apt-get install --no-install-recommends -q -y build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev bison autoconf ruby + if: ${{ contains(matrix.os, 'ubuntu') }} + - name: Install libraries + run: | + 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: .downloaded-cache + key: downloaded-cache + - run: ./autogen.sh + - name: Run configure + run: ./configure -C --disable-install-doc --disable-rubygems --with-gcc 'optflags=-O0' 'debugflags=-save-temps=obj -g' + - run: make all golf + - run: ruby tool/update-deps --fix + - run: git diff --no-ext-diff --ignore-submodules --exit-code + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 + with: + payload: | + { + "ci": "GitHub Actions", + "env": "${{ matrix.os }} / Dependencies need to 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/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000000..8dba76fbe2 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,75 @@ +name: "Code scanning - action" + +on: + # push: + # paths-ignore: + # - 'doc/**' + # - '**/man' + # - '**.md' + # - '**.rdoc' + # - '**/.document' + # pull_request: + # paths-ignore: + # - 'doc/**' + # - '**/man' + # - '**.md' + # - '**.rdoc' + # - '**/.document' + schedule: + - 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 + # 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 + + steps: + - name: Install libraries + run: | + set -x + sudo apt-get update -q || : + sudo apt-get install --no-install-recommends -q -y build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev bison autoconf ruby + + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: .downloaded-cache + key: downloaded-cache + + - name: Remove an obsolete rubygems vendored file + run: sudo rm /usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb + + - name: Initialize CodeQL + 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@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2.1.37 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2.1.37 diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml new file mode 100644 index 0000000000..caf12cc0f4 --- /dev/null +++ b/.github/workflows/compilers.yml @@ -0,0 +1,281 @@ +name: Compilations + +on: + push: + paths-ignore: + - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + pull_request: + paths-ignore: + - 'doc/**' + - '**/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 +# environment variables (plus the "echo $GITHUB_ENV" hack) is to reroute that +# restriction. +env: + default_cc: clang-15 + append_cc: '' + + # -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 + # 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' + + # -g0 disables backtraces when SEGV. Do not set that. + debugflags: '-ggdb3' + + default_configure: >- + --enable-debug-env + --disable-install-doc + --with-ext=-test-/cxxanyargs,+ + append_configure: >- + --without-valgrind + --without-jemalloc + --without-gmp + + UPDATE_UNICODE: >- + UNICODE_FILES=. + UNICODE_PROPERTY_FILES=. + UNICODE_AUXILIARY_FILES=. + UNICODE_EMOJI_FILES=. + CONFIGURE_TTY: never + GITPULLOPTIONS: --no-tags origin ${{github.ref}} + RUBY_DEBUG: ci rgengc + RUBY_TESTOPTS: >- + -q + --color=always + --tty=no + +permissions: + contents: read + +jobs: + compile: + strategy: + fail-fast: false + matrix: + env: + - {} + entry: + - { 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 + - { 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 + +# - { 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 || matrix.entry.env.default_cc || 'clang-15' }} + options: --user root + 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: + - run: mkdir build + working-directory: + - name: setenv + run: | + echo "GNUMAKEFLAGS=-sj$((1 + $(nproc --all)))" >> $GITHUB_ENV + - 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 + - run: ./autogen.sh + working-directory: src + - name: Run configure + run: > + ../src/configure -C ${default_configure} ${append_configure} + --${{ + 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 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: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 + with: + payload: | + { + "ci": "GitHub Actions", + "env": "${{ github.workflow }} / ${{ matrix.entry.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/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 new file mode 100644 index 0000000000..0df917d3d8 --- /dev/null +++ b/.github/workflows/mingw.yml @@ -0,0 +1,179 @@ +name: MinGW +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 +# +jobs: + make: + runs-on: windows-2022 + name: ${{ github.workflow }} (${{ matrix.msystem }}) + env: + MSYSTEM: ${{ matrix.msystem }} + MSYS2_ARCH: x86_64 + CHOST: "x86_64-w64-mingw32" + 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" + UPDATE_UNICODE: "UNICODE_FILES=. UNICODE_PROPERTY_FILES=. UNICODE_AUXILIARY_FILES=. UNICODE_EMOJI_FILES=." + GITPULLOPTIONS: --no-tags origin ${{github.ref}} + strategy: + matrix: + include: + # To mitigate flakiness of MinGW CI, we test only one runtime that newer MSYS2 uses. + - msystem: "UCRT64" + base_ruby: head + test_task: "check" + test-all-opts: "--name=!/TestObjSpace#test_reachable_objects_during_iteration/" + fail-fast: false + 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 core.autocrlf false + git config --global core.eol lf + 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: Set up Ruby & MSYS2 + 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 + + - 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' + where $e || result=false + echo '##['endgroup']' + 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 + working-directory: src + + - name: configure + run: > + ../src/configure --disable-install-doc --prefix=/. + --build=$CHOST --host=$CHOST --target=$CHOST + + - name: update + run: | + make incs + + - name: download gems + run: | + make update-gems + + - name: make all + timeout-minutes: 30 + run: | + make + + - run: make leaked-globals + + - name: make install + run: | + make DESTDIR=../install install-nodoc + + - name: test + 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 ${{ StartsWith(matrix.test_task, 'test/') && matrix.test_task || 'test-all' }} + env: + 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 ${{ 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: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 + with: + payload: | + { + "ci": "GitHub Actions", + "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_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 + shell: sh 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 new file mode 100644 index 0000000000..6f7181489a --- /dev/null +++ b/.github/workflows/mjit.yml @@ -0,0 +1,113 @@ +name: MJIT +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 + mjit_opts: [--mjit-wait] + fail-fast: false + runs-on: ubuntu-latest + 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.mjit_opts }} --mjit-debug=-ggdb3' + GITPULLOPTIONS: --no-tags origin ${{github.ref}} + steps: + - run: mkdir build + working-directory: + - name: Install libraries + run: | + set -x + sudo apt-get update -q || : + sudo apt-get install --no-install-recommends -q -y build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev bison autoconf ruby + - 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' || : + - name: Set ENV + run: | + echo "GNUMAKEFLAGS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV + - run: ./autogen.sh + working-directory: src + - name: Run configure + run: ../src/configure -C --disable-install-doc cppflags=-DVM_CHECK_MODE + - run: make incs + - run: make + - run: sudo make -s install + - name: Run test + run: | + 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-spec + run: | + unset GNUMAKEFLAGS + make -s test-spec RUN_OPTS="$RUN_OPTS" + timeout-minutes: 60 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 + with: + payload: | + { + "ci": "GitHub Actions", + "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_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/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 new file mode 100644 index 0000000000..4521195a2b --- /dev/null +++ b/.github/workflows/spec_guards.yml @@ -0,0 +1,71 @@ +name: Rubyspec Version Guards Check + +on: + push: + paths: + - 'spec/**' + - '!spec/*.md' + pull_request: + 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-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-3.1 + - ruby-3.2 + + steps: + - 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 + env: + CHECK_LEAKS: true + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 + with: + payload: | + { + "ci": "GitHub Actions", + "env": "${{ github.workflow }} / rubyspec @ ${{ matrix.ruby }}", + "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() }} diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml new file mode 100644 index 0000000000..4fbca1170e --- /dev/null +++ b/.github/workflows/ubuntu.yml @@ -0,0 +1,146 @@ +name: Ubuntu +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: + # 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 + - 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 + 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 + run: | + echo "GNUMAKEFLAGS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV + - name: Install libraries + env: + arch: ${{matrix.arch}} + run: | + set -x + arch=${arch:+:${arch/i[3-6]86/i386}} + ${arch:+sudo dpkg --add-architecture ${arch#:}} + sudo apt-get update -q || : + sudo apt-get install --no-install-recommends -q -y \ + ${arch:+cross}build-essential${arch/:/-} \ + libssl-dev${arch} libyaml-dev${arch} libreadline6-dev${arch} \ + zlib1g-dev${arch} libncurses5-dev${arch} libffi-dev${arch} \ + bison autoconf ruby + sudo apt-get install -q -y pkg-config${arch} || : + - 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 + env: + arch: ${{matrix.arch}} + run: >- + $SETARCH ../src/configure -C --disable-install-doc ${{ matrix.configure }} + ${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' }} + - 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|$|$$/|'`} + 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|$|$$/|'` + env: + GNUMAKEFLAGS: "" + RUBY_TESTOPTS: "-v --tty=no" + TESTS: ${{ matrix.skipped_tests }} + 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": "${{ 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_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/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 new file mode 100644 index 0000000000..c2bd4881c2 --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,149 @@ +name: Windows +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: 2022 + vcvers: -vcvars_ver=14.2 + fail-fast: false + 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}} + PATCH: C:\msys64\usr\bin\patch.exe + OS_VER: windows-${{ matrix.vs }} + steps: + - run: md build + working-directory: + - uses: msys2/setup-msys2@61f9e5e925871ba6c9e3e8da24ede83ea27fa91f # v2.27.0 + id: setup-msys2 + with: + update: true + 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@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: C:\vcpkg\installed + key: ${{ runner.os }}-vcpkg-installed-windows-${{ matrix.vs }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-vcpkg-installed-windows-${{ matrix.vs }}- + ${{ runner.os }}-vcpkg-installed-windows- + - name: Install libraries with vcpkg + run: | + 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + path: src + - 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% ${{ 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: compiler version + run: cl + - name: link libraries + run: | + 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: 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 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 + with: + payload: | + { + "ci": "GitHub Actions", + "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_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 + shell: cmd diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml new file mode 100644 index 0000000000..0b7b9046e9 --- /dev/null +++ b/.github/workflows/yjit-ubuntu.yml @@ -0,0 +1,170 @@ +name: YJIT Ubuntu +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: + 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" + configure: "--enable-yjit=dev" + + - test_task: "test-bundled-gems" + 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 + 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: + - name: Install libraries + run: | + set -x + sudo apt-get update -q || : + sudo apt-get install --no-install-recommends -q -y build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev bison autoconf 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@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' || : + - name: Set ENV + run: | + echo "GNUMAKEFLAGS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV + - run: ./autogen.sh + working-directory: src + - name: Run configure + run: ../src/configure -C --disable-install-doc --prefix=$(pwd)/install ${{ matrix.configure }} + - run: make incs + - 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: | + ./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: Enable YJIT through ENV + run: echo "RUBY_YJIT_ENABLE=1" >> $GITHUB_ENV + # 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: "" + 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": "${{ github.workflow }} / ${{ 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/.gitignore b/.gitignore index 50bf27f28a..99d32a1825 100644 --- a/.gitignore +++ b/.gitignore @@ -10,23 +10,26 @@ *.dylib *.elc *.i +*.ii *.inc *.log *.o +*.o.tmp *.obj *.old *.orig *.pch *.pdb +*.rbinc *.rej *.s *.sav *.sl *.so +*.so.* *.swp *.yarb *~ -.*-* .*.list .*.time .DS_Store @@ -36,6 +39,8 @@ .pc .ppack .svn +.time +.ruby-version Makefile cygruby*.def extconf.h @@ -44,6 +49,7 @@ y.tab.c *.gcda *.gcno *.gcov +*.vscode lcov*.info # / @@ -54,7 +60,11 @@ lcov*.info /*.pc /*.rc /*_prelude.c +/.downloaded-cache +/.top-enc.mk +/build*/ /COPYING.LIB +/ChangeLog /Doxyfile /GNUmakefile /README.atheos @@ -112,14 +122,17 @@ lcov*.info /rename2.h /repack /revision.h +/revision.tmp /riscos /rubicon /ruby /ruby-runner /ruby-runner.h /ruby-man.rd.gz +/rubyspec_temp /run.gdb /sizes.c +/static-ruby /test.rb /test-coverage.dat /tmp @@ -130,6 +143,13 @@ lcov*.info /web /yasmdata.rb +# /bin/ + +/bin/*.exe +/bin/*.dll +/bin/goruby +/bin/ruby + # /benchmark/ /benchmark/bm_require.data /benchmark/bmx_*.rb @@ -144,30 +164,33 @@ lcov*.info # /coroutine/ !/coroutine/**/*.s -/coroutine/**/.time # /enc/trans/ /enc/trans/*.c /enc/trans/*.def /enc/trans/*.exp /enc/trans/*.lib -/enc/trans/.time # /exe/ /exe/goruby /exe/ruby -/exe/.time # /ext/ /ext/extinit.c /ext/configure-ext.mk /ext/*/exts.mk +# /ext/-test-/cxxanyargs +/ext/-test-/cxxanyargs/failure*.failed + # /ext/-test-/win32/dln/ /ext/-test-/win32/dln/dlntest.dll /ext/-test-/win32/dln/dlntest.exp /ext/-test-/win32/dln/dlntest.lib +# /ext/-test-/gems +/ext/-test-/gems + # /ext/etc/ /ext/etc/constdefs.h @@ -180,6 +203,7 @@ lcov*.info # /ext/ripper/ /ext/ripper/eventids1.c +/ext/ripper/.eventids2-check /ext/ripper/eventids2table.c /ext/ripper/ripper.* /ext/ripper/ids1 @@ -195,6 +219,13 @@ lcov*.info /gems/src /gems/*-* +# /lib/ +/lib/ruby/[1-9]*.* +/lib/ruby/vendor_ruby + +# /misc/ +/misc/**/__pycache__ + # /spec/bundler /.rspec_status @@ -204,9 +235,16 @@ lcov*.info # /win32/ /win32/*.ico -/win32/.time # 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 index 6a207a0554..1d61cbcad1 100644 --- a/.indent.pro +++ b/.indent.pro @@ -2,12 +2,15 @@ -nbbb -nbc -br +-brs -nbs -ncdb --ce --cli0.5 +-nce +-cdw +-cli2 +-cbi2 -ndj --ei +-ncs -nfc1 -i4 -l120 @@ -16,6 +19,14 @@ -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/.rspec_parallel b/.rspec_parallel new file mode 100644 index 0000000000..aaff198a32 --- /dev/null +++ b/.rspec_parallel @@ -0,0 +1,2 @@ +--format progress +--format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 03c423ef2f..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,415 +0,0 @@ -# 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. - -# This is a Travis-CI build configuration file. The list of configurations -# available is located in -# -# http://about.travis-ci.org/docs/user/build-configuration/ -# -# and as Ruby itself is a project written in C language, -# -# http://about.travis-ci.org/docs/user/languages/c/ -# -# is also a good place to look at. - -language: c - -dist: xenial - -osx_image: xcode10.1 - -sudo: false - -git: - quiet: true - -addons: - apt: - config: - retries: true - update: true - sources: - - ubuntu-toolchain-r-test - packages: - - gcc-8 - - libffi-dev - - libgdbm-dev - - libgmp-dev - - libjemalloc-dev - - libncurses5-dev - - libncursesw5-dev - - libreadline6-dev - - libssl-dev - - libyaml-dev - - openssl - - valgrind - - zlib1g-dev - homebrew: - packages: - - gdbm - - gmp - - libffi - - openssl@1.1 - - zlib - - ccache - -cache: - ccache: true - directories: - - $HOME/config_2nd - - $HOME/.downloaded-cache - -env: - global: - - CONFIGURE_TTY=no - - CCACHE_COMPILERCHECK=none - - CCACHE_NOCOMPRESS=1 - - CCACHE_MAXSIZE=512Mi - - >- - NPROC="`case ${TRAVIS_OS_NAME} in - osx) sysctl -n hw.activecpu ;; - linux) nproc ;; - esac`" - # JOBS and SETARCH are overridden when necessary; see below. - - JOBS=-j$((1+${NPROC})) - - SETARCH= - - RUBY_PREFIX=/tmp/ruby-prefix - - GEMS_FOR_TEST='timezone tzinfo' - -.org.ruby-lang.ci.matrix-definitions: - - - &cron-only - if: (type = cron) AND (branch = trunk) AND (fork = false) - - - &make-test-only - script: - - $SETARCH make -s test TESTOPTS="${TESTOPTS=$JOBS -q --tty=no}" - - - &linux - os: linux - compiler: gcc-8 - - - &osx - os: osx - compiler: clang - before_install: - - /usr/local/opt/openssl@1.1/bin/openssl version - - # -------- - - - &x86_64-linux - name: x86_64-linux - <<: *linux - - - &jemalloc - name: --with-jemalloc - <<: *linux - <<: *cron-only - env: - - CONFIG_FLAG='--with-gmp --with-jemalloc --with-valgrind' - - - &VM_CHECK_MODE - name: VM_CHECK_MODE=3 - <<: *linux - <<: *cron-only - <<: *make-test-only - env: - - cppflags=-DVM_CHECK_MODE=0x0003 - - - &FIBER_USE_sjlj - name: FIBER_USE_NATIVE=0 - <<: *linux - <<: *cron-only - env: - - cppflags=-DFIBER_USE_NATIVE=0 - - - &TOKEN_THREADED_CODE - name: TOKEN_THREADED_CODE - <<: *linux - <<: *cron-only - <<: *make-test-only - env: - - cppflags=-DOPT_THREADED_CODE=1 - - - &CALL_THREADED_CODE - name: CALL_THREADED_CODE - <<: *linux - <<: *cron-only - <<: *make-test-only - env: - - cppflags=-DOPT_THREADED_CODE=2 - - - &NO_THREADED_CODE - name: NO_THREADED_CODE - <<: *linux - <<: *cron-only - <<: *make-test-only - env: - - cppflags=-DOPT_THREADED_CODE=3 - - - &ASAN - name: -fsanitize=address - <<: *linux - #<<: *cron-only - <<: *make-test-only - compiler: clang - env: - - ASAN_OPTIONS=detect_leaks=0 - - cflags='-march=native -fsanitize=address -fno-omit-frame-pointer' - - debugflags=-ggdb3 - - optflags=-O1 - - LD=clang - - LDFLAGS=-fsanitize=address - - - &MSAN - name: -fsanitize=memory - <<: *linux - #<<: *cron-only - <<: *make-test-only - compiler: clang - env: - - cflags='-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer' - - optflags=-O1 - - LD=clang - - LDFLAGS=-fsanitize=memory - - CONFIG_FLAG=--with-out-ext=openssl - - - &UBSAN - name: -fsanitize=undefined - <<: *linux - #<<: *cron-only - <<: *make-test-only - compiler: clang - env: - - cflags='-fsanitize=undefined,integer,nullability -fno-omit-frame-pointer' - - cppflags=-DUNALIGNED_WORD_ACCESS=0 - - debugflags=-ggdb3 - - optflags='-O1 -march=native' - - LD=clang - - LDFLAGS=-fsanitize=undefined,integer,nullability - - - &i686-linux - name: i686-linux - <<: *linux - sudo: required - env: - - GCC_FLAGS=-m32 - - debugflags=-g0 - - SETARCH='setarch i686 --verbose --3gb' - addons: - apt: - config: - retries: true - update: true - sources: - - ubuntu-toolchain-r-test - packages: - - gcc-8-multilib - - libffi-dev:i386 - - libffi6:i386 - - libgdbm-dev:i386 - - libgdbm3:i386 - - libncurses5-dev:i386 - - libncurses5:i386 - - libncursesw5-dev:i386 - - libreadline6-dev:i386 - - libreadline6:i386 - - libssl-dev:i386 - - libssl1.0.0:i386 - - linux-libc-dev:i386 - - zlib1g-dev:i386 - - zlib1g:i386 - - - &pedanticism - name: -ansi -pedantic - <<: *linux - <<: *make-test-only - compiler: clang - env: - - GCC_FLAGS='-ansi -Werror=pedantic -pedantic-errors -std=iso9899:1990' - - CONFIG_FLAG= - - JOBS= - - >- - warnflags=' - -Wall - -Wextra - -Werror=declaration-after-statement - -Werror=deprecated-declarations - -Werror=division-by-zero - -Werror=extra-tokens - -Werror=implicit-function-declaration - -Werror=implicit-int - -Werror=long-long - -Werror=pointer-arith - -Werror=shorten-64-to-32 - -Werror=write-strings - -Wmissing-noreturn - -Wno-constant-logical-operand - -Wno-missing-field-initializers - -Wno-overlength-strings - -Wno-parentheses-equality - -Wno-self-assign - -Wno-tautological-compare - -Wno-unused-local-typedef - -Wno-unused-parameter - -Wunused-variable - ' - - LDFLAGS=-Wno-unused-command-line-argument - - - &rubyspec - name: ruby/spec on Ruby 2.3 # to ensure version guards are correctly added - <<: *linux - language: ruby - rvm: 2.3.8 - addons: - apt: - packages: - before_install: - install: - before_script: chmod -R u+w spec/ruby - # -j randomly hangs. Using -fs to make sure we can know problematic spec on failure. - script: ruby -C spec/ruby ../mspec/bin/mspec -fs . - - - &x86_64-darwin17 - name: x86_64-darwin17 - <<: *osx - env: - - CONFIG_FLAG=--with-opt-dir=/usr/local/opt/openssl@1.1:/usr/local/opt/zlib - - TEST_ALL_OPTS="--tty=no --excludes=\$(TESTSDIR)/excludes/_travis/osx" - # Disabling -j3 because it seems to cause a hang on building Ruby: https://travis-ci.org/ruby/ruby/jobs/471021727 - - JOBS= - - - &universal-darwin17 - name: uinversal.x86_64h-darwin17 - <<: *osx - <<: *cron-only - <<: *make-test-only - env: - - CONFIG_FLAG=--with-arch=x86_64h,x86_64,i386 - - TEST_ALL_OPTS="$JOBS -q --tty=no --excludes=\$(TESTSDIR)/excludes/_travis/osx" - -matrix: - include: - # to reduce time for finishing all jobs, run the slowest osx build first. - - <<: *x86_64-darwin17 - - <<: *x86_64-linux - - <<: *i686-linux - - <<: *jemalloc - - <<: *pedanticism - - <<: *ASAN - - <<: *MSAN - - <<: *UBSAN - - <<: *VM_CHECK_MODE - - <<: *FIBER_USE_sjlj - - <<: *TOKEN_THREADED_CODE - - <<: *CALL_THREADED_CODE - - <<: *NO_THREADED_CODE - - <<: *rubyspec - allow_failures: - - name: uinversal.x86_64h-darwin17 - - name: -fsanitize=address - - name: -fsanitize=memory - - name: -fsanitize=undefined - fast_finish: true - -before_script: - - echo JOBS=${JOBS} SETARCH=${SETARCH} - - $SETARCH uname -a - - $SETARCH uname -r - - rm -fr .ext autom4te.cache - - echo $TERM - - |- - [ -d ~/.downloaded-cache ] || - mkdir ~/.downloaded-cache - - ln -s ~/.downloaded-cache - - "> config.status" - - sed -f tool/prereq.status Makefile.in common.mk > Makefile - - make -s $JOBS update-download - - make -s $JOBS srcs - - rm 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 - - ccache --show-stats - - |- - 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 install - - ccache --show-stats - - |- - [ -z "${GEMS_FOR_TEST}" ] || - $RUBY_PREFIX/bin/gem install --no-document $GEMS_FOR_TEST - -script: - - $SETARCH make -s test TESTOPTS="${TESTOPTS=$JOBS -q --tty=no}" - - travis_wait 30 $SETARCH make -s test-all -o exts TESTOPTS="${TEST_ALL_OPTS=$TESTOPTS}" - - $SETARCH make -s test-spec MSPECOPT=-ff # not using `-j` because sometimes `mspec -j` silently dies - -# Branch matrix. Not all branches are Travis-ready so we limit branches here. -branches: - only: - - trunk - - ruby_2_3 - - ruby_2_4 - - ruby_2_5 - - ruby_2_6 - - /^feature\// - - /^bug\// - - /^travis-/ - -# We want to be notified when something happens. -notifications: - irc: - channels: - - "chat.freenode.net#ruby-core" - - "chat.freenode.net#ruby-ja" - on_success: change # [always|never|change] # default: always - on_failure: always # [always|never|change] # default: always - template: - - "%{message} by @%{author}: See %{build_url}" - - slack: - rooms: - - secure: i1GLETSKye85ea6dGNA3MxI/5myChmMFiZtBd5C69xK+s1sBFqEgOSbaSf9KHc0CYrHVyNhQMaZRruieV7xS+6Pfs0Zvxf1DO6QQTWC2KhkqwFDLvZncAzjoyASdR90hbr+iRPOngQ+HJuE94zemALAwEqNAinzA74PMiJXktqY= # ruby:<token>#commits - - secure: ah7UEHBvncXT7bM5mvYIQAO+tIyV/wl7nXLb7wQD16dO2v8Gragy0mWjB79Q09hrrMGmp6H9bCDpdGS80boIA5EHaHoG4QaP0i9bsSt8U2AMWgZtfyIgQKJ4H2kXkGlrjO+AXTgnIkP7LNjdgAVUUTGQPb26T3QmoN2Splt+fIQ= # ruby:<token>#alerts - on_pull_requests: false - on_success: change - on_failure: always - - email: - - ko1c-failure@atdot.net - -# Local Variables: -# mode: YAML -# coding: utf-8-unix -# indent-tabs-mode: nil -# tab-width: 4 -# fill-column: 79 -# default-justification: full -# End: @@ -4,10 +4,10 @@ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. + notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ffdf2dd4b8..13df6087ca 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1 @@ -Please see the [official issue tracker] and wiki [HowToContribute]. - -[official issue tracker]: https://bugs.ruby-lang.org -[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. @@ -2,55 +2,55 @@ Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>. You can redistribute it and/or modify it under either the terms of the 2-clause BSDL (see the file BSDL), or the conditions below: - 1. You may make and give away verbatim copies of the source form of the - software without restriction, provided that you duplicate all of the - original copyright notices and associated disclaimers. +1. You may make and give away verbatim copies of the source form of the + software without restriction, provided that you duplicate all of the + original copyright notices and associated disclaimers. - 2. You may modify your copy of the software in any way, provided that - you do at least ONE of the following: +2. You may modify your copy of the software in any way, provided that + you do at least ONE of the following: - a) place your modifications in the Public Domain or otherwise - make them Freely Available, such as by posting said - modifications to Usenet or an equivalent medium, or by allowing - the author to include your modifications in the software. + a. place your modifications in the Public Domain or otherwise + make them Freely Available, such as by posting said + modifications to Usenet or an equivalent medium, or by allowing + the author to include your modifications in the software. - b) use the modified software only within your corporation or - organization. + b. use the modified software only within your corporation or + organization. - c) give non-standard binaries non-standard names, with - instructions on where to get the original software distribution. + c. give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. - d) make other distribution arrangements with the author. + d. make other distribution arrangements with the author. - 3. You may distribute the software in object code or binary form, - provided that you do at least ONE of the following: +3. You may distribute the software in object code or binary form, + provided that you do at least ONE of the following: - a) distribute the binaries and library files of the software, - together with instructions (in the manual page or equivalent) - on where to get the original distribution. + a. distribute the binaries and library files of the software, + together with instructions (in the manual page or equivalent) + on where to get the original distribution. - b) accompany the distribution with the machine-readable source of - the software. + b. accompany the distribution with the machine-readable source of + the software. - c) give non-standard binaries non-standard names, with - instructions on where to get the original software distribution. + c. give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. - d) make other distribution arrangements with the author. + d. make other distribution arrangements with the author. - 4. You may modify and include the part of the software into any other - software (possibly commercial). But some files in the distribution - are not written by the author, so that they are not under these terms. +4. You may modify and include the part of the software into any other + software (possibly commercial). But some files in the distribution + are not written by the author, so that they are not under these terms. - For the list of those files and their copying conditions, see the - file LEGAL. + For the list of those files and their copying conditions, see the + file LEGAL. - 5. The scripts and library files supplied as input to or produced as - output from the software do not automatically fall under the - copyright of the software, but belong to whomever generated them, - and may be sold commercially, and may be aggregated with this - software. +5. The scripts and library files supplied as input to or produced as + output from the software do not automatically fall under the + copyright of the software, but belong to whomever generated them, + and may be sold commercially, and may be aggregated with this + software. - 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR - IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - PURPOSE. +6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. diff --git a/COPYING.ja b/COPYING.ja index e50d01c8d1..230376bc60 100644 --- a/COPYING.ja +++ b/COPYING.ja @@ -2,50 +2,50 @@ ã¾ãŸã¯ä»¥ä¸‹ã«ç¤ºã™æ¡ä»¶ã§æœ¬ãƒ—ãƒã‚°ãƒ©ãƒ ã‚’å†é…布ã§ãã¾ã™ 2-clause BSDLã«ã¤ã„ã¦ã¯BSDLファイルをå‚ç…§ã—ã¦ä¸‹ã•ã„. - 1. 複製ã¯åˆ¶é™ãªã自由ã§ã™ï¼Ž +1. 複製ã¯åˆ¶é™ãªã自由ã§ã™ï¼Ž - 2. ä»¥ä¸‹ã®æ¡ä»¶ã®ã„ãšã‚Œã‹ã‚’満ãŸã™æ™‚ã«æœ¬ãƒ—ãƒã‚°ãƒ©ãƒ ã®ã‚½ãƒ¼ã‚¹ã‚’ - 自由ã«å¤‰æ›´ã§ãã¾ã™ï¼Ž +2. ä»¥ä¸‹ã®æ¡ä»¶ã®ã„ãšã‚Œã‹ã‚’満ãŸã™æ™‚ã«æœ¬ãƒ—ãƒã‚°ãƒ©ãƒ ã®ã‚½ãƒ¼ã‚¹ã‚’ + 自由ã«å¤‰æ›´ã§ãã¾ã™ï¼Ž - (a) ãƒãƒƒãƒˆãƒ‹ãƒ¥ãƒ¼ã‚ºã«ãƒã‚¹ãƒˆã—ãŸã‚Šï¼Œä½œè€…ã«å¤‰æ›´ã‚’é€ä»˜ã™ã‚‹ - ãªã©ã®æ–¹æ³•ã§ï¼Œå¤‰æ›´ã‚’公開ã™ã‚‹ï¼Ž + a. ãƒãƒƒãƒˆãƒ‹ãƒ¥ãƒ¼ã‚ºã«ãƒã‚¹ãƒˆã—ãŸã‚Šï¼Œä½œè€…ã«å¤‰æ›´ã‚’é€ä»˜ã™ã‚‹ + ãªã©ã®æ–¹æ³•ã§ï¼Œå¤‰æ›´ã‚’公開ã™ã‚‹ï¼Ž - (b) 変更ã—ãŸæœ¬ãƒ—ãƒã‚°ãƒ©ãƒ ã‚’è‡ªåˆ†ã®æ‰€å±žã™ã‚‹çµ„織内部ã ã‘ã§ - 使ã†ï¼Ž + b. 変更ã—ãŸæœ¬ãƒ—ãƒã‚°ãƒ©ãƒ ã‚’è‡ªåˆ†ã®æ‰€å±žã™ã‚‹çµ„織内部ã ã‘ã§ + 使ã†ï¼Ž - (c) 変更点を明示ã—ãŸã†ãˆï¼Œã‚½ãƒ•トウェアã®åå‰ã‚’変更ã™ã‚‹ï¼Ž - ãã®ã‚½ãƒ•トウェアをé…布ã™ã‚‹æ™‚ã«ã¯å¤‰æ›´å‰ã®æœ¬ãƒ—ãƒã‚°ãƒ© - ãƒ ã‚‚åŒæ™‚ã«é…布ã™ã‚‹ï¼Žã¾ãŸã¯å¤‰æ›´å‰ã®æœ¬ãƒ—ãƒã‚°ãƒ©ãƒ ã®ã‚½ãƒ¼ - スã®å…¥æ‰‹æ³•を明示ã™ã‚‹ï¼Ž + c. 変更点を明示ã—ãŸã†ãˆï¼Œã‚½ãƒ•トウェアã®åå‰ã‚’変更ã™ã‚‹ï¼Ž + ãã®ã‚½ãƒ•トウェアをé…布ã™ã‚‹æ™‚ã«ã¯å¤‰æ›´å‰ã®æœ¬ãƒ—ãƒã‚°ãƒ© + ãƒ ã‚‚åŒæ™‚ã«é…布ã™ã‚‹ï¼Žã¾ãŸã¯å¤‰æ›´å‰ã®æœ¬ãƒ—ãƒã‚°ãƒ©ãƒ ã®ã‚½ãƒ¼ + スã®å…¥æ‰‹æ³•を明示ã™ã‚‹ï¼Ž - (d) ãã®ä»–ã®å¤‰æ›´æ¡ä»¶ã‚’作者ã¨åˆæ„ã™ã‚‹ï¼Ž + d. ãã®ä»–ã®å¤‰æ›´æ¡ä»¶ã‚’作者ã¨åˆæ„ã™ã‚‹ï¼Ž - 3. ä»¥ä¸‹ã®æ¡ä»¶ã®ã„ãšã‚Œã‹ã‚’満ãŸã™æ™‚ã«æœ¬ãƒ—ãƒã‚°ãƒ©ãƒ をコンパイ - ルã—ãŸã‚ªãƒ–ジェクトコードや実行形å¼ã§ã‚‚é…布ã§ãã¾ã™ï¼Ž +3. ä»¥ä¸‹ã®æ¡ä»¶ã®ã„ãšã‚Œã‹ã‚’満ãŸã™æ™‚ã«æœ¬ãƒ—ãƒã‚°ãƒ©ãƒ をコンパイ + ルã—ãŸã‚ªãƒ–ジェクトコードや実行形å¼ã§ã‚‚é…布ã§ãã¾ã™ï¼Ž - (a) ãƒã‚¤ãƒŠãƒªã‚’å—ã‘å–ã£ãŸäººãŒã‚½ãƒ¼ã‚¹ã‚’入手ã§ãるよã†ã«ï¼Œ - ソースã®å…¥æ‰‹æ³•を明示ã™ã‚‹ï¼Ž + a. ãƒã‚¤ãƒŠãƒªã‚’å—ã‘å–ã£ãŸäººãŒã‚½ãƒ¼ã‚¹ã‚’入手ã§ãるよã†ã«ï¼Œ + ソースã®å…¥æ‰‹æ³•を明示ã™ã‚‹ï¼Ž - (b) 機械å¯èªãªã‚½ãƒ¼ã‚¹ã‚³ãƒ¼ãƒ‰ã‚’添付ã™ã‚‹ï¼Ž + b. 機械å¯èªãªã‚½ãƒ¼ã‚¹ã‚³ãƒ¼ãƒ‰ã‚’添付ã™ã‚‹ï¼Ž - (c) 変更を行ã£ãŸãƒã‚¤ãƒŠãƒªã¯åå‰ã‚’変更ã—ãŸã†ãˆï¼Œã‚ªãƒªã‚¸ãƒŠ - ルã®ã‚½ãƒ¼ã‚¹ã‚³ãƒ¼ãƒ‰ã®å…¥æ‰‹æ³•を明示ã™ã‚‹ï¼Ž + c. 変更を行ã£ãŸãƒã‚¤ãƒŠãƒªã¯åå‰ã‚’変更ã—ãŸã†ãˆï¼Œã‚ªãƒªã‚¸ãƒŠ + ルã®ã‚½ãƒ¼ã‚¹ã‚³ãƒ¼ãƒ‰ã®å…¥æ‰‹æ³•を明示ã™ã‚‹ï¼Ž - (d) ãã®ä»–ã®é…布æ¡ä»¶ã‚’作者ã¨åˆæ„ã™ã‚‹ï¼Ž + d. ãã®ä»–ã®é…布æ¡ä»¶ã‚’作者ã¨åˆæ„ã™ã‚‹ï¼Ž - 4. ä»–ã®ãƒ—ãƒã‚°ãƒ©ãƒ ã¸ã®å¼•用ã¯ã„ã‹ãªã‚‹ç›®çš„ã§ã‚れ自由ã§ã™ï¼ŽãŸ - ã ã—,本プãƒã‚°ãƒ©ãƒ ã«å«ã¾ã‚Œã‚‹ä»–ã®ä½œè€…ã«ã‚ˆã‚‹ã‚³ãƒ¼ãƒ‰ã¯ï¼Œã - れãžã‚Œã®ä½œè€…ã®æ„å‘ã«ã‚ˆã‚‹åˆ¶é™ãŒåŠ ãˆã‚‰ã‚Œã‚‹å ´åˆãŒã‚りã¾ã™ï¼Ž +4. ä»–ã®ãƒ—ãƒã‚°ãƒ©ãƒ ã¸ã®å¼•用ã¯ã„ã‹ãªã‚‹ç›®çš„ã§ã‚れ自由ã§ã™ï¼ŽãŸ + ã ã—,本プãƒã‚°ãƒ©ãƒ ã«å«ã¾ã‚Œã‚‹ä»–ã®ä½œè€…ã«ã‚ˆã‚‹ã‚³ãƒ¼ãƒ‰ã¯ï¼Œã + れãžã‚Œã®ä½œè€…ã®æ„å‘ã«ã‚ˆã‚‹åˆ¶é™ãŒåŠ ãˆã‚‰ã‚Œã‚‹å ´åˆãŒã‚りã¾ã™ï¼Ž - ãれらファイルã®ä¸€è¦§ã¨ãれãžã‚Œã®é…布æ¡ä»¶ãªã©ã«ä»˜ã„ã¦ã¯ - LEGALファイルをå‚ç…§ã—ã¦ãã ã•ã„. + ãれらファイルã®ä¸€è¦§ã¨ãれãžã‚Œã®é…布æ¡ä»¶ãªã©ã«ä»˜ã„ã¦ã¯ + LEGALファイルをå‚ç…§ã—ã¦ãã ã•ã„. - 5. 本プãƒã‚°ãƒ©ãƒ ã¸ã®å…¥åŠ›ã¨ãªã‚‹ã‚¹ã‚¯ãƒªãƒ—トãŠã‚ˆã³ï¼Œæœ¬ãƒ—ãƒã‚°ãƒ© - ムã‹ã‚‰ã®å‡ºåŠ›ã®æ¨©åˆ©ã¯æœ¬ãƒ—ãƒã‚°ãƒ©ãƒ ã®ä½œè€…ã§ã¯ãªã,ãれ㞠- れã®å…¥å‡ºåŠ›ã‚’ç”Ÿæˆã—ãŸäººã«å±žã—ã¾ã™ï¼Žã¾ãŸï¼Œæœ¬ãƒ—ãƒã‚°ãƒ©ãƒ ã« - 組ã¿è¾¼ã¾ã‚Œã‚‹ãŸã‚ã®æ‹¡å¼µãƒ©ã‚¤ãƒ–ラリã«ã¤ã„ã¦ã‚‚åŒæ§˜ã§ã™ï¼Ž +5. 本プãƒã‚°ãƒ©ãƒ ã¸ã®å…¥åŠ›ã¨ãªã‚‹ã‚¹ã‚¯ãƒªãƒ—トãŠã‚ˆã³ï¼Œæœ¬ãƒ—ãƒã‚°ãƒ© + ムã‹ã‚‰ã®å‡ºåŠ›ã®æ¨©åˆ©ã¯æœ¬ãƒ—ãƒã‚°ãƒ©ãƒ ã®ä½œè€…ã§ã¯ãªã,ãれ㞠+ れã®å…¥å‡ºåŠ›ã‚’ç”Ÿæˆã—ãŸäººã«å±žã—ã¾ã™ï¼Žã¾ãŸï¼Œæœ¬ãƒ—ãƒã‚°ãƒ©ãƒ ã« + 組ã¿è¾¼ã¾ã‚Œã‚‹ãŸã‚ã®æ‹¡å¼µãƒ©ã‚¤ãƒ–ラリã«ã¤ã„ã¦ã‚‚åŒæ§˜ã§ã™ï¼Ž - 6. 本プãƒã‚°ãƒ©ãƒ ã¯ç„¡ä¿è¨¼ã§ã™ï¼Žä½œè€…ã¯æœ¬ãƒ—ãƒã‚°ãƒ©ãƒ をサãƒãƒ¼ãƒˆ - ã™ã‚‹æ„å¿—ã¯ã‚りã¾ã™ãŒï¼Œãƒ—ãƒã‚°ãƒ©ãƒ 自身ã®ãƒã‚°ã‚ã‚‹ã„ã¯æœ¬ãƒ— - ãƒã‚°ãƒ©ãƒ ã®å®Ÿè¡Œãªã©ã‹ã‚‰ç™ºç”Ÿã™ã‚‹ã„ã‹ãªã‚‹æå®³ã«å¯¾ã—ã¦ã‚‚責 - 任をæŒã¡ã¾ã›ã‚“. +6. 本プãƒã‚°ãƒ©ãƒ ã¯ç„¡ä¿è¨¼ã§ã™ï¼Žä½œè€…ã¯æœ¬ãƒ—ãƒã‚°ãƒ©ãƒ をサãƒãƒ¼ãƒˆ + ã™ã‚‹æ„å¿—ã¯ã‚りã¾ã™ãŒï¼Œãƒ—ãƒã‚°ãƒ©ãƒ 自身ã®ãƒã‚°ã‚ã‚‹ã„ã¯æœ¬ãƒ— + ãƒã‚°ãƒ©ãƒ ã®å®Ÿè¡Œãªã©ã‹ã‚‰ç™ºç”Ÿã™ã‚‹ã„ã‹ãªã‚‹æå®³ã«å¯¾ã—ã¦ã‚‚責 + 任をæŒã¡ã¾ã›ã‚“. @@ -7,92 +7,118 @@ All the files in this distribution are covered under either the Ruby's license (see the file COPYING) or public-domain except some files mentioned below. -ccan/build_assert/build_assert.h:: -ccan/check_type/check_type.h:: -ccan/container_of/container_of.h:: -ccan/str/str.h:: +[addr2line.c] - These files are licensed under the CC0. + A part of this file is from FreeBSD. >>> - https://creativecommons.org/choose/zero/ + Copyright (c) 1986, 1988, 1991, 1993:: + The Regents of the University of California. All rights reserved. -ccan/list/list.h:: + (c) UNIX System Laboratories, Inc. - This file is licensed under the MIT License. + All or some portions of this file are derived from material licensed + to the University of California by American Telephone and Telegraph + Co. or Unix System Laboratories, Inc. and are reproduced herein with + the permission of UNIX System Laboratories, Inc. - >>> - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 4. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - -include/ruby/onigmo.h:: -include/ruby/oniguruma.h:: -regcomp.c:: -regenc.c:: -regenc.h:: -regerror.c:: -regexec.c:: -regint.h:: -regparse.c:: -regparse.h:: -enc/ascii.c:: -enc/big5.c:: -enc/cp949.c:: -enc/emacs_mule.c:: -enc/encdb.c:: -enc/euc_jp.c:: -enc/euc_kr.c:: -enc/euc_tw.c:: -enc/gb18030.c:: -enc/gb2312.c:: -enc/gbk.c:: -enc/iso_8859_1.c:: -enc/iso_8859_10.c:: -enc/iso_8859_11.c:: -enc/iso_8859_13.c:: -enc/iso_8859_14.c:: -enc/iso_8859_15.c:: -enc/iso_8859_16.c:: -enc/iso_8859_2.c:: -enc/iso_8859_3.c:: -enc/iso_8859_4.c:: -enc/iso_8859_5.c:: -enc/iso_8859_6.c:: -enc/iso_8859_7.c:: -enc/iso_8859_8.c:: -enc/iso_8859_9.c:: -enc/koi8_r.c:: -enc/koi8_u.c:: -enc/shift_jis.c:: -enc/unicode.c:: -enc/us_ascii.c:: -enc/utf_16be.c:: -enc/utf_16le.c:: -enc/utf_32be.c:: -enc/utf_32le.c:: -enc/utf_8.c:: -enc/windows_1251.c:: + @(#)subr_prf.c 8.3 (Berkeley) 1/21/94 + + +[ccan/build_assert/build_assert.h] +[ccan/check_type/check_type.h] +[ccan/container_of/container_of.h] +[ccan/str/str.h] + + These files are licensed under the {CC0}[https://creativecommons.org/choose/zero/]. + +[ccan/list/list.h] + + This file is licensed under the {MIT License}[rdoc-label:label-MIT+License]. + +[coroutine] + + Unless otherwise specified, these files are licensed under the + {MIT License}[rdoc-label:label-MIT+License]. + +[include/ruby/onigmo.h] +[include/ruby/oniguruma.h] +[regcomp.c] +[regenc.c] +[regenc.h] +[regerror.c] +[regexec.c] +[regint.h] +[regparse.c] +[regparse.h] +[enc/ascii.c] +[enc/big5.c] +[enc/cp949.c] +[enc/emacs_mule.c] +[enc/encdb.c] +[enc/euc_jp.c] +[enc/euc_kr.c] +[enc/euc_tw.c] +[enc/gb18030.c] +[enc/gb2312.c] +[enc/gbk.c] +[enc/iso_8859_1.c] +[enc/iso_8859_10.c] +[enc/iso_8859_11.c] +[enc/iso_8859_13.c] +[enc/iso_8859_14.c] +[enc/iso_8859_15.c] +[enc/iso_8859_16.c] +[enc/iso_8859_2.c] +[enc/iso_8859_3.c] +[enc/iso_8859_4.c] +[enc/iso_8859_5.c] +[enc/iso_8859_6.c] +[enc/iso_8859_7.c] +[enc/iso_8859_8.c] +[enc/iso_8859_9.c] +[enc/koi8_r.c] +[enc/koi8_u.c] +[enc/shift_jis.c] +[enc/unicode.c] +[enc/us_ascii.c] +[enc/utf_16be.c] +[enc/utf_16le.c] +[enc/utf_32be.c] +[enc/utf_32le.c] +[enc/utf_8.c] +[enc/windows_1251.c] +[enc/windows_31j.c] Onigmo (Oniguruma-mod) LICENSE >>> - Copyright (c) 2002-2009 K.Kosako <sndgk393 AT ybb DOT ne DOT jp> - Copyright (c) 2011-2014 K.Takata <kentkt AT csc DOT jp> + Copyright (c) 2002-2009:: K.Kosako <sndgk393 AT ybb DOT ne DOT jp> + Copyright (c) 2011-2014:: K.Takata <kentkt AT csc DOT jp> All rights reserved. Redistribution and use in source and binary forms, with or without @@ -119,7 +145,7 @@ enc/windows_1251.c:: Oniguruma LICENSE >>> - Copyright (c) 2002-2009 K.Kosako <sndgk393 AT ybb DOT ne DOT jp> + Copyright (c) 2002-2009:: K.Kosako <sndgk393 AT ybb DOT ne DOT jp> All rights reserved. Redistribution and use in source and binary forms, with or without @@ -150,10 +176,69 @@ enc/windows_1251.c:: When this software is partly used or it is distributed with Ruby, this of Ruby follows the license of Ruby. -enc/trans/GB/GB12345%UCS.src:: -enc/trans/GB/UCS%GB12345.src:: -enc/trans/GB/GB2312%UCS.src:: -enc/trans/GB/UCS%GB2312.src:: +[enc/windows_1250.c] +[enc/windows_1252.c] + + >>> + Copyright (c) 2006-2007:: Byte <byte AT mail DOT kna DOT ru> + K.Kosako <sndgk393 AT ybb DOT ne DOT jp> + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + +[enc/cesu_8.c] +[enc/windows_1253.c] +[enc/windows_1254.c] +[enc/windows_1257.c] + + >>> + Copyright (c) 2002-2007:: K.Kosako <sndgk393 AT ybb DOT ne DOT jp> + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + +[enc/trans/GB/GB12345%UCS.src] +[enc/trans/GB/UCS%GB12345.src] +[enc/trans/GB/GB2312%UCS.src] +[enc/trans/GB/UCS%GB2312.src] These files have this explanatory texts. @@ -165,12 +250,12 @@ enc/trans/GB/UCS%GB2312.src:: files for any particular purpose, and has ceased to publish the files many years ago. -enc/trans/JIS/JISX0201-KANA%UCS.src:: -enc/trans/JIS/JISX0208\@1990%UCS.src:: -enc/trans/JIS/JISX0212%UCS.src:: -enc/trans/JIS/UCS%JISX0201-KANA.src:: -enc/trans/JIS/UCS%JISX0208@1990.src:: -enc/trans/JIS/UCS%JISX0212.src:: +[enc/trans/JIS/JISX0201-KANA%UCS.src] +[enc/trans/JIS/JISX0208\@1990%UCS.src] +[enc/trans/JIS/JISX0212%UCS.src] +[enc/trans/JIS/UCS%JISX0201-KANA.src] +[enc/trans/JIS/UCS%JISX0208@1990.src] +[enc/trans/JIS/UCS%JISX0212.src] These files are copyrighted as the following. @@ -179,49 +264,64 @@ enc/trans/JIS/UCS%JISX0212.src:: For terms of use, see http://www.unicode.org/terms_of_use.html -enc/trans/JIS/JISX0213-1%UCS@BMP.src:: -enc/trans/JIS/JISX0213-1%UCS@SIP.src:: -enc/trans/JIS/JISX0213-2%UCS@BMP.src:: -enc/trans/JIS/JISX0213-2%UCS@SIP.src:: +[enc/trans/JIS/JISX0213-1%UCS@BMP.src] +[enc/trans/JIS/JISX0213-1%UCS@SIP.src] +[enc/trans/JIS/JISX0213-2%UCS@BMP.src] +[enc/trans/JIS/JISX0213-2%UCS@SIP.src] These files are copyrighted as the following. >>> - Copyright (C) 2001 earthian@tama.or.jp, All Rights Reserved. - Copyright (C) 2001 I'O, All Rights Reserved. - Copyright (C) 2006 Project X0213, All Rights Reserved. + Copyright (C) 2001:: earthian@tama.or.jp, All Rights Reserved. + Copyright (C) 2001:: I'O, All Rights Reserved. + Copyright (C) 2006:: Project X0213, All Rights Reserved. You can use, modify, distribute this table freely. -enc/trans/JIS/UCS@BMP%JISX0213-1.src:: -enc/trans/JIS/UCS@BMP%JISX0213-2.src:: -enc/trans/JIS/UCS@SIP%JISX0213-1.src:: -enc/trans/JIS/UCS@SIP%JISX0213-2.src:: +[enc/trans/JIS/UCS@BMP%JISX0213-1.src] +[enc/trans/JIS/UCS@BMP%JISX0213-2.src] +[enc/trans/JIS/UCS@SIP%JISX0213-1.src] +[enc/trans/JIS/UCS@SIP%JISX0213-2.src] These files are copyrighted as the following. >>> - Copyright (C) 2001 earthian@tama.or.jp, All Rights Reserved. - Copyright (C) 2001 I'O, All Rights Reserved. + Copyright (C) 2001:: earthian@tama.or.jp, All Rights Reserved. + Copyright (C) 2001:: I'O, All Rights Reserved. You can use, modify, distribute this table freely. -configure:: +[enc/trans/ucm/glibc-BIG5-2.3.3.ucm] +[enc/trans/ucm/glibc-BIG5HKSCS-2.3.3.ucm] + + >>> + Copyright (C) 2001-2005:: International Business Machines + Corporation and others. All Rights Reserved. + +[enc/trans/ucm/windows-950-2000.ucm] +[enc/trans/ucm/windows-950_hkscs-2001.ucm] + + >>> + Copyright (C) 2001-2002:: International Business Machines + Corporation and others. All Rights Reserved. + + +[configure] This file is free software. >>> - Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. + Copyright (C) 1992-1996, 1998-2012:: Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. -tool/config.guess:: -tool/config.sub:: +[tool/config.guess] +[tool/config.sub] As long as you distribute these files with the file configure, they are covered under the Ruby's license. >>> - Copyright 1992-2018 Free Software Foundation, Inc. + Copyright 1992-2018:: Free Software Foundation, Inc. This file is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -243,14 +343,43 @@ tool/config.sub:: program. This Exception is an additional permission under section 7 of the GNU General Public License, version 3 ("GPLv3"). -parse.c:: +[tool/lib/test/*] +[tool/lib/core_assertions.rb] + + Some of methods on these files are based on MiniTest 4. MiniTest 4 is + distributed under the MIT License. + + >>> + Copyright (c) Ryan Davis, seattle.rb + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + 'Software'), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - This file is licensed under the GPL, but is incorporated into Ruby and +[parse.c] +[parse.h] + + These files are licensed under the GPL, but are incorporated into Ruby and redistributed under the terms of the Ruby license, as permitted by the exception to the GPL below. >>> - Copyright (C) 1984, 1989-1990, 2000-2015, 2018 Free Software Foundation, Inc. + Copyright (C) 1984, 1989-1990, 2000-2015, 2018:: Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -278,12 +407,12 @@ parse.c:: This special exception was added by the Free Software Foundation in version 2.2 of Bison. -util.c (partly):: +[missing/dtoa.c] - This file contains the source code under these licenses. + This file is under these licenses. >>> - Copyright (c) 1991, 2000, 2001 by Lucent Technologies. + Copyright (c) 1991, 2000, 2001:: by Lucent Technologies. Permission to use, copy, modify, and distribute this software for any purpose without fee is hereby granted, provided that this entire notice @@ -297,8 +426,8 @@ util.c (partly):: OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. >>> - Copyright (c) 2004-2008 David Schultz <das@FreeBSD.ORG> - All rights reserved. + Copyright (c) 2004-2008:: David Schultz <das@FreeBSD.ORG> + All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -321,32 +450,34 @@ util.c (partly):: OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -win32/win32.{c,h}:: +[win32/win32.c] +[include/ruby/win32.h] You can apply the Artistic License to these files. (or GPL, alternatively) >>> - Copyright (c) 1993, Intergraph Corporation + Copyright (c) 1993:: Intergraph Corporation You may distribute under the terms of either the GNU General Public License or the Artistic License, as specified in the perl README file. -random.c:: +[missing/mt19937.c] - This file contains the source code under the new-style BSD license. + This file is under the new-style BSD license. >>> - A C-program for MT19937, with initialization improved 2002/2/10. - Coded by Takuji Nishimura and Makoto Matsumoto. + A C-program for MT19937, with initialization improved 2002/2/10.:: + Coded by Takuji Nishimura and Makoto Matsumoto. + This is a faster version by taking Shawn Cokus's optimization, Matthe Bellew's simplification, Isaku Wada's real version. Before using, initialize the state by using init_genrand(seed) or init_by_array(init_key, key_length). - Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, - All rights reserved. + Copyright (C) 1997 - 2002:: Makoto Matsumoto and Takuji Nishimura, + All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -382,13 +513,13 @@ random.c:: The Wayback Machine url: http://web.archive.org/web/19990429082237/http://www.math.keio.ac.jp/matumoto/emt.html -vm_dump.c:procstat_vm:: +[missing/procstat_vm.c] - This file contains the source code under the new-style BSD license. + This file is under the new-style BSD license. >>> - Copyright (c) 2007 Robert N. M. Watson - All rights reserved. + Copyright (c) 2007:: Robert N. M. Watson + All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -413,151 +544,65 @@ vm_dump.c:procstat_vm:: $FreeBSD: head/usr.bin/procstat/procstat_vm.c 261780 2014-02-11 21:57:37Z jhb $ -vsnprintf.c:: +[vsnprintf.c] - This file is under the old-style BSD license. Note that the - paragraph 3 below is now null and void. + This file is under the {old-style BSD license}[rdoc-label:label-Old-style+BSD+license]. >>> - Copyright (c) 1990, 1993 + Copyright (c) 1990, 1993:: The Regents of the University of California. All rights reserved. This code is derived from software contributed to Berkeley by Chris Torek. - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - 3. Neither the name of the University nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - SUCH DAMAGE. - - IMPORTANT NOTE: - -------------- - From ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change - paragraph 3 above is now null and void. - -st.c:: -strftime.c:: -include/ruby/st.h:: -missing/acosh.c:: -missing/alloca.c:: -missing/dup2.c:: -missing/erf.c:: -missing/finite.c:: -missing/hypot.c:: -missing/isinf.c:: -missing/isnan.c:: -missing/lgamma_r.c:: -missing/memcmp.c:: -missing/memmove.c:: -missing/strchr.c:: -missing/strerror.c:: -missing/strstr.c:: -missing/tgamma.c:: -ext/date/date_strftime.c:: -ext/digest/sha1/sha1.c:: -ext/digest/sha1/sha1.h:: -ext/sdbm/_sdbm.c:: -ext/sdbm/sdbm.h:: +[st.c] +[strftime.c] +[include/ruby/st.h] +[missing/acosh.c] +[missing/alloca.c] +[missing/erf.c] +[missing/hypot.c] +[missing/lgamma_r.c] +[missing/memcmp.c] +[missing/memmove.c] +[missing/strchr.c] +[missing/strerror.c] +[missing/strstr.c] +[missing/tgamma.c] +[ext/date/date_strftime.c] +[ext/digest/sha1/sha1.c] +[ext/digest/sha1/sha1.h] These files are all under public domain. -missing/crypt.c:: +[missing/crypt.c] - This file is under the old-style BSD license. Note that the - paragraph 3 below is now null and void. + This file is under the {old-style BSD license}[rdoc-label:label-Old-style+BSD+license]. >>> - Copyright (c) 1989, 1993 + Copyright (c) 1989, 1993:: The Regents of the University of California. All rights reserved. This code is derived from software contributed to Berkeley by Tom Truscott. - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - 3. Neither the name of the University nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - SUCH DAMAGE. - -missing/setproctitle.c:: +[missing/setproctitle.c] - This file is under the old-style BSD license. Note that the - paragraph 3 below is now null and void. + This file is under the {old-style BSD license}[rdoc-label:label-Old-style+BSD+license]. >>> - Copyright 2003 Damien Miller - Copyright (c) 1983, 1995-1997 Eric P. Allman - Copyright (c) 1988, 1993 + Copyright 2003:: Damien Miller + Copyright (c) 1983, 1995-1997:: Eric P. Allman + Copyright (c) 1988, 1993:: The Regents of the University of California. All rights reserved. - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - 3. Neither the name of the University nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - SUCH DAMAGE. - -missing/strlcat.c:: -missing/strlcpy.c:: +[missing/strlcat.c] +[missing/strlcpy.c] These files are under an ISC-style license. >>> - Copyright (c) 1998, 2015 Todd C. Miller <Todd.Miller@courtesan.com> + Copyright (c) 1998, 2015:: Todd C. Miller <Todd.Miller@courtesan.com> Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -571,26 +616,26 @@ missing/strlcpy.c:: ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -missing/langinfo.c:: +[missing/langinfo.c] This file is from http://www.cl.cam.ac.uk/~mgk25/ucs/langinfo.c. Ruby uses a modified version. The file contains the following author/copyright notice: >>> - Markus.Kuhn@cl.cam.ac.uk -- 2002-03-11 + Markus.Kuhn@cl.cam.ac.uk -- 2002-03-11:: Permission to use, copy, modify, and distribute this software for any purpose and without fee is hereby granted. The author disclaims all warranties with regard to this software. -ext/digest/md5/md5.c:: -ext/digest/md5/md5.h:: +[ext/digest/md5/md5.c] +[ext/digest/md5/md5.h] These files are under the following license. Ruby uses modified versions of them. >>> - Copyright (C) 1999, 2000 Aladdin Enterprises. All rights reserved. + Copyright (C) 1999, 2000:: Aladdin Enterprises. All rights reserved. This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -611,8 +656,8 @@ ext/digest/md5/md5.h:: L. Peter Deutsch ghost@aladdin.com -ext/digest/rmd160/rmd160.c:: -ext/digest/rmd160/rmd160.h:: +[ext/digest/rmd160/rmd160.c] +[ext/digest/rmd160/rmd160.h] These files have the following copyright information, and by the author we are allowed to use it under the new-style BSD license. @@ -622,16 +667,16 @@ ext/digest/rmd160/rmd160.h:: (Arranged for libc by Todd C. Miller) DATE:: 1 March 1996 - Copyright (c) Katholieke Universiteit Leuven + Copyright (c):: Katholieke Universiteit Leuven 1996, All Rights Reserved -ext/digest/sha2/sha2.c:: -ext/digest/sha2/sha2.h:: +[ext/digest/sha2/sha2.c] +[ext/digest/sha2/sha2.h] These files are under the new-style BSD license. >>> - Copyright 2000 Aaron D. Gifford. All rights reserved. + Copyright 2000:: Aaron D. Gifford. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -657,12 +702,12 @@ ext/digest/sha2/sha2.h:: OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -ext/json/generator/generator.c:: +[ext/json/generator/generator.c] The file contains the following copyright notice. >>> - Copyright 2001-2004 Unicode, Inc. + Copyright 2001-2004:: Unicode, Inc. Disclaimer:: @@ -682,15 +727,15 @@ ext/json/generator/generator.c:: for internal or external distribution as long as this notice remains attached. -ext/nkf/nkf-utf8/config.h:: -ext/nkf/nkf-utf8/nkf.c:: -ext/nkf/nkf-utf8/utf8tbl.c:: +[ext/nkf/nkf-utf8/config.h] +[ext/nkf/nkf-utf8/nkf.c] +[ext/nkf/nkf-utf8/utf8tbl.c] These files are under the following license. So to speak, it is copyrighted semi-public-domain software. >>> - Copyright (C) 1987, Fujitsu LTD. (Itaru ICHIKAWA) + Copyright (C) 1987:: Fujitsu LTD. (Itaru ICHIKAWA) Everyone is permitted to do anything on this program including copying, modifying, improving, @@ -700,14 +745,14 @@ ext/nkf/nkf-utf8/utf8tbl.c:: You don't have to ask before copying, redistribution or publishing. THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE. -ext/psych:: -test/psych:: +[ext/psych] +[test/psych] The files under these directories are under the following license, except for ext/psych/yaml. >>> - Copyright 2009 Aaron Patterson, et al. + Copyright 2009:: Aaron Patterson, et al. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in @@ -727,12 +772,12 @@ test/psych:: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -ext/psych/yaml:: +[ext/psych/yaml] The files under this directory are under the following license. >>> - Copyright (c) 2006 Kirill Simonov + Copyright (c) 2006:: Kirill Simonov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -752,14 +797,28 @@ ext/psych/yaml:: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -ext/socket/addrinfo.h:: -ext/socket/getaddrinfo.c:: -ext/socket/getnameinfo.c:: +[ext/pty/pty.c] + + >>> + C) Copyright 1998:: by Akinori Ito. + + This software may be redistributed freely for this purpose, in full + or in part, provided that this entire copyright notice is included + on any copies of this software and applications and derivations thereof. + + This software is provided on an "as is" basis, without warranty of any + kind, either expressed or implied, as to any matter including, but not + limited to warranty of fitness of purpose, or merchantability, or + results obtained from use of this software. + +[ext/socket/addrinfo.h] +[ext/socket/getaddrinfo.c] +[ext/socket/getnameinfo.c] These files are under the new-style BSD license. >>> - Copyright (C) 1995, 1996, 1997, 1998, and 1999 WIDE Project. + Copyright (C) 1995, 1996, 1997, 1998, and 1999:: WIDE Project. All rights reserved. Redistribution and use in source and binary forms, with or without @@ -786,16 +845,16 @@ ext/socket/getnameinfo.c:: OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -ext/win32ole/win32ole.c:: +[ext/win32ole/win32ole.c] You can apply the Artistic License to this file. (or GPL, alternatively) >>> - (c) 1995 Microsoft Corporation. All rights reserved. + (c) 1995:: Microsoft Corporation. All rights reserved. Developed by ActiveWare Internet Corp., http://www.ActiveWare.com - Other modifications Copyright (c) 1997, 1998 by Gurusamy Sarathy + Other modifications Copyright (c) 1997, 1998:: by Gurusamy Sarathy <gsar@umich.edu> and Jan Dubois <jan.dubois@ibm.net> You may distribute under the terms of either the GNU General Public @@ -804,20 +863,17 @@ ext/win32ole/win32ole.c:: The Wayback Machine url: http://web.archive.org/web/19970607104352/http://www.activeware.com:80/ -lib/rdoc/generator/template/darkfish/css/fonts.css:: +[lib/rdoc/generator/template/darkfish/css/fonts.css] - This file is licensed under the SIL Open Font License. + This file is licensed under the {SIL Open Font License}[http://scripts.sil.org/OFL]. - >>> - http://scripts.sil.org/OFL - -spec/mspec:: -spec/ruby:: +[spec/mspec] +[spec/ruby] The files under these directories are under the following license. >>> - Copyright (c) 2008 Engine Yard, Inc. All rights reserved. + Copyright (c) 2008:: Engine Yard, Inc. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -840,17 +896,17 @@ spec/ruby:: FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -lib/rubygems.rb:: -lib/rubygems:: -test/rubygems:: +[lib/rubygems.rb] +[lib/rubygems] +[test/rubygems] RubyGems is under the following license. >>> RubyGems is copyrighted free software by Chad Fowler, Rich Kilmer, Jim Weirich and others. You can redistribute it and/or modify it under - either the terms of the MIT license (see the file MIT.txt), or the - conditions below: + either the terms of the {MIT license}[rdoc-label:label-MIT+License], or the conditions + below: 1. You may make and give away verbatim copies of the source form of the software without restriction, provided that you duplicate all of the @@ -901,20 +957,117 @@ test/rubygems:: WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -lib/bundler:: -lib/bundler.rb:: -lib/bundler.gemspec:: -spec/bundler:: -man/bundle-*,gemfile.*:: +[lib/bundler] +[lib/bundler.rb] +[spec/bundler] Bundler is under the following license. >>> - Portions copyright (c) 2010 Andre Arko - Portions copyright (c) 2009 Engine Yard + Portions copyright (c) 2010:: Andre Arko + Portions copyright (c) 2009:: Engine Yard + + {MIT License}[rdoc-label:label-MIT+License] + +[lib/bundler/vendor/thor] + + Thor is under the following license. + + >>> + Copyright (c) 2008 Yehuda Katz, Eric Hodel, et al. + + {MIT License}[rdoc-label:label-MIT+License] + +[lib/rubygems/resolver/molinillo] + + molinillo is under the following license. + + >>> + Copyright (c) 2014 Samuel E. Giddins segiddins@segiddins.me + + {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. + + >>> + Copyright (c) 2011 Mike Perham + + {MIT License}[rdoc-label:label-MIT+License] + +[lib/bundler/vendor/net-http-persistent] + + net-http-persistent is under the following license. + + >>> + Copyright (c) Eric Hodel, Aaron Patterson - MIT License:: + {MIT License}[rdoc-label:label-MIT+License] +[lib/did_you_mean] +[lib/did_you_mean.rb] +[test/did_you_mean] + + did_you_mean is under the following license. + + >>> + Copyright (c) 2014-2016 Yuki Nishijima + + {MIT License}[rdoc-label:label-MIT+License] + +[lib/error_highlight] +[lib/error_highlight.rb] +[test/error_highlight] + + error_highlight is under the following license. + + >>> + Copyright (c) 2021 Yusuke Endoh + + {MIT License}[rdoc-label:label-MIT+License] + +[benchmark/so_ackermann.rb] +[benchmark/so_array.rb] +[benchmark/so_binary_trees.rb] +[benchmark/so_concatenate.rb] +[benchmark/so_count_words.yml] +[benchmark/so_exception.rb] +[benchmark/so_fannkuch.rb] +[benchmark/so_fasta.rb] +[benchmark/so_k_nucleotide.yml] +[benchmark/so_lists.rb] +[benchmark/so_mandelbrot.rb] +[benchmark/so_matrix.rb] +[benchmark/so_meteor_contest.rb] +[benchmark/so_nbody.rb] +[benchmark/so_nested_loop.rb] +[benchmark/so_nsieve.rb] +[benchmark/so_nsieve_bits.rb] +[benchmark/so_object.rb] +[benchmark/so_partial_sums.rb] +[benchmark/so_pidigits.rb] +[benchmark/so_random.rb] +[benchmark/so_reverse_complement.yml] +[benchmark/so_sieve.rb] +[benchmark/so_spectralnorm.rb] + + These files are very old copy of then-called "The Great Computer Language + Shootout". LEGAL SITUATION OF THESE FILES ARE UNCLEAR because the original + site has been lost. Upstream diverged to delete several benchmarks listed + above. + +== MIT License +>>> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -933,3 +1086,34 @@ man/bundle-*,gemfile.*:: LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +== Old-style BSD license +>>> + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + IMPORTANT NOTE:: + + From ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change + paragraph 3 above is now null and void. diff --git a/Makefile.in b/Makefile.in deleted file mode 100644 index fa1e19ef37..0000000000 --- a/Makefile.in +++ /dev/null @@ -1,633 +0,0 @@ -SHELL = /bin/sh -NULLCMD = @NULLCMD@ -n=$(NULLCMD) -ECHO1 = $(V:1=$n) -RUNCMD = $(SHELL) -CDPATH = . -CHDIR = @CHDIR@ -exec = exec -NULL = /dev/null -PATH_SEPARATOR = @PATH_SEPARATOR@ - -#### Start of system configuration section. #### - -srcdir = @srcdir@ -top_srcdir = $(srcdir) -hdrdir = $(srcdir)/include -PLATFORM_DIR = @PLATFORM_DIR@ - -CC_WRAPPER = @XCC_WRAPPER@ -CC = @CC@ -CPP = @CPP@ -LD = @LD@ -YACC = bison -PURIFY = -AUTOCONF = autoconf -ACLOCAL = aclocal -CONFIGURE = @CONFIGURE@ -@SET_MAKE@ -MKFILES = @MAKEFILES@ -BASERUBY = @BASERUBY@ -HAVE_BASERUBY = @HAVE_BASERUBY@ -TEST_RUNNABLE = @TEST_RUNNABLE@ -CROSS_COMPILING = @CROSS_COMPILING@ -DOXYGEN = @DOXYGEN@ - -prefix = @prefix@ -exec_prefix = @exec_prefix@ -bindir = @bindir@ -sbindir = @sbindir@ -libdir = @libdir@ -libexecdir = @libexecdir@ -datarootdir = @datarootdir@ -datadir = @datadir@ -arch = @arch@ -sitearch = @sitearch@ -sitedir = @sitedir@ -archlibdir = @archlibdir@ -includedir = @includedir@ -archincludedir = @archincludedir@ -rubylibprefix = @rubylibprefix@ -rubylibdir = @rubylibdir@ -rubyarchprefix = @rubyarchprefix@ -rubyarchdir = @rubyarchdir@ -rubyhdrdir = @rubyhdrdir@ -rubyarchhdrdir = @rubyarchhdrdir@ -ruby_version = @ruby_version@ -RUBY_VERSION_NAME = @RUBY_VERSION_NAME@ -UNIVERSAL_ARCHNAMES = @UNIVERSAL_ARCHNAMES@ - -TESTUI = console -TESTS = -INSTALLDOC = @INSTALLDOC@ -DOCTARGETS = @RDOCTARGET@ @CAPITARGET@ - -EXTOUT = @EXTOUT@ -TIMESTAMPDIR = $(EXTOUT)/.timestamp -arch_hdrdir = $(EXTOUT)/include/$(arch) -VPATH = $(arch_hdrdir)/ruby:$(hdrdir)/ruby:$(srcdir):$(srcdir)/missing - -empty = -CC_VERSION = @CC_VERSION@ -OUTFLAG = @OUTFLAG@$(empty) -COUTFLAG = @COUTFLAG@$(empty) -CPPOUTFLAG = $(COUTFLAG) -ARCH_FLAG = @ARCH_FLAG@ -CFLAGS_NO_ARCH = @CFLAGS@ -CFLAGS = $(CFLAGS_NO_ARCH) $(ARCH_FLAG) -cflags = @cflags@ -optflags = @optflags@ -debugflags = @debugflags@ -warnflags = @warnflags@ @strict_warnflags@ -cppflags = @cppflags@ -MATHN = @MATHN@ -XCFLAGS = @XCFLAGS@ $(MATHN:yes=-DCANONICALIZATION_FOR_MATHN) -USE_RUBYGEMS = @USE_RUBYGEMS@ -USE_RUBYGEMS_ = $(USE_RUBYGEMS:yes=) -CPPFLAGS = @CPPFLAGS@ $(INCFLAGS) $(USE_RUBYGEMS_:no=-DDISABLE_RUBYGEMS=1) -MJIT_SUPPORT = @MJIT_SUPPORT@ -MJIT_HEADER_FLAGS = @MJIT_HEADER_FLAGS@ -MJIT_HEADER_SUFFIX = -MJIT_HEADER_ARCH = -MJIT_HEADER_INSTALL_DIR = @MJIT_HEADER_INSTALL_DIR@ -MJIT_CC = @MJIT_CC@ -MJIT_CFLAGS = @MJIT_CFLAGS@ -MJIT_OPTFLAGS = @MJIT_OPTFLAGS@ -MJIT_DEBUGFLAGS = @MJIT_DEBUGFLAGS@ -MJIT_LDSHARED = @MJIT_LDSHARED@ -MJIT_DLDFLAGS = @DLDFLAGS@ -MJIT_HEADER = rb_mjit_header.h -MJIT_MIN_HEADER_NAME = rb_mjit_min_header-$(RUBY_PROGRAM_VERSION).h -MJIT_MIN_HEADER = $(MJIT_HEADER_BUILD_DIR)/$(MJIT_MIN_HEADER_NAME) -MJIT_HEADER_BUILD_DIR = $(EXTOUT)/include/$(arch) -LDFLAGS = @STATIC@ $(CFLAGS) @LDFLAGS@ -EXTLDFLAGS = @EXTLDFLAGS@ -XLDFLAGS = @XLDFLAGS@ $(EXTLDFLAGS) -EXTLIBS = -LIBS = @LIBS@ $(EXTLIBS) -MISSING = @LIBOBJS@ @ALLOCA@ -ENABLE_SHARED = @ENABLE_SHARED@ -LDSHARED = @LIBRUBY_LDSHARED@ -DLDSHARED = @DLDSHARED@ -DLDFLAGS = @LIBRUBY_DLDFLAGS@ $(XLDFLAGS) $(ARCH_FLAG) -SOLIBS = @SOLIBS@ -ENABLE_DEBUG_ENV = @ENABLE_DEBUG_ENV@ -MAINLIBS = @MAINLIBS@ -ARCHMINIOBJS = @MINIOBJS@ -DLNOBJ = @DLNOBJ@ -ENCOBJS = @ENCOBJS@ -EXTOBJS = @EXTOBJS@ -BUILTIN_ENCOBJS = @BUILTIN_ENCOBJS@ -BUILTIN_TRANSSRCS = @BUILTIN_TRANSSRCS@ -BUILTIN_TRANSOBJS = @BUILTIN_TRANSOBJS@ -POSTLINK = @POSTLINK@ - -RUBY_BASE_NAME=@RUBY_BASE_NAME@ -RUBY_PROGRAM_VERSION=@RUBY_PROGRAM_VERSION@ -RUBY_API_VERSION=@RUBY_API_VERSION@ -RUBY_INSTALL_NAME=@RUBY_INSTALL_NAME@ -RUBY_SO_NAME=@RUBY_SO_NAME@ -EXEEXT = @EXEEXT@ -LIBEXT = @LIBEXT@ -PROGRAM=$(RUBY_INSTALL_NAME)$(EXEEXT) -RUBY = $(RUBY_INSTALL_NAME) -MINIRUBY = @MINIRUBY@\ - $(MINIRUBYOPT) -# RUNRUBY_COMMAND:: runruby.rb or baseruby. do not append options directly -RUNRUBY_COMMAND = @RUNRUBY_COMMAND@ -# RUNRUBY:: run ruby with RUN_OPTS which is passed to ruby -RUNRUBY = @RUNRUBY@ $(RUN_OPTS) -# RUNRUBY_DEBUGGER:: debugging option for runruby.rb -RUNRUBY_DEBUGGER = --debugger='gdb -x run.gdb --quiet --args' -XRUBY = @XRUBY@ -BTESTRUBY = @BTESTRUBY@\ - $(MINIRUBYOPT) -INSTALL = @INSTALL@ -INSTALL_DATA = @INSTALL_DATA@ -INSTALL_SCRIPT = @INSTALL_SCRIPT@ -INSTALL_PROGRAM = @INSTALL_PROGRAM@ -XRUBY_LIBDIR = @XRUBY_LIBDIR@ -XRUBY_RUBYLIBDIR = @XRUBY_RUBYLIBDIR@ -XRUBY_RUBYHDRDIR = @XRUBY_RUBYHDRDIR@ -BOOTSTRAPRUBY = @BOOTSTRAPRUBY@ - -COROUTINE_H = @X_FIBER_COROUTINE_H@ -COROUTINE_OBJ = $(COROUTINE_H:.h=.@OBJEXT@) - -#### End of system configuration section. #### - -MAJOR= @MAJOR@ -MINOR= @MINOR@ -TEENY= @TEENY@ -RUBY_PROGRAM_VERSION = @RUBY_PROGRAM_VERSION@ - -LIBRUBY_A = @LIBRUBY_A@ -LIBRUBY_SO = @LIBRUBY_SO@ -LIBRUBY_SONAME= @LIBRUBY_SONAME@ -LIBRUBY_ALIASES= @LIBRUBY_ALIASES@ -LIBRUBY = @LIBRUBY@ -LIBRUBYARG = @LIBRUBYARG@ -LIBRUBYARG_STATIC = @LIBRUBYARG_STATIC@ -LIBRUBYARG_SHARED = @LIBRUBYARG_SHARED@ -LIBRUBY_RELATIVE = @LIBRUBY_RELATIVE@ -LIBRUBY_A_OBJS = @LIBRUBY_A_OBJS@ - -DTRACE_REBUILD_OBJS = $(DTRACE_REBUILD:yes=$(DTRACE_DEPENDENT_OBJS)) - -DTRACE_DEPENDENT_OBJS = array.$(OBJEXT) \ - eval.$(OBJEXT) \ - gc.$(OBJEXT) \ - hash.$(OBJEXT) \ - load.$(OBJEXT) \ - object.$(OBJEXT) \ - parse.$(OBJEXT) \ - string.$(OBJEXT) \ - symbol.$(OBJEXT) \ - vm.$(OBJEXT) - -THREAD_MODEL = @THREAD_MODEL@ - -PREP = @PREP@ -ARCHFILE = @ARCHFILE@ -SETUP = -EXTSTATIC = @EXTSTATIC@ -ENCSTATIC = @ENCSTATIC@ -SET_LC_MESSAGES = env LC_MESSAGES=C - -MAKEDIRS = @MKDIR_P@ -CP = cp -MV = mv -RM = rm -f -RMDIR = @RMDIR@ -RMDIRS = @RMDIRS@ -RMALL = @RMALL@ -NM = @NM@ -AR = @AR@ -ARFLAGS = @ARFLAGS@$(empty) -RANLIB = @RANLIB@ -AS = @AS@ -ASFLAGS = @ASFLAGS@ $(INCFLAGS) -IFCHANGE = $(srcdir)/tool/ifchange -OBJDUMP = @OBJDUMP@ -OBJCOPY = @OBJCOPY@ -HAVE_GIT = @HAVE_GIT@ -GIT = @GIT@ -VCS = @VCS@ -VCSUP = @VCSUP@ -DTRACE = @DTRACE@ @DTRACE_OPT@ -DTRACE_EXT = @DTRACE_EXT@ -DTRACE_OBJ = @DTRACE_OBJ@ -DTRACE_REBUILD= @DTRACE_REBUILD@ -DTRACE_GLOMMED_OBJ = $(DTRACE_REBUILD:yes=ruby-glommed.$(OBJEXT)) - -OBJEXT = @OBJEXT@ -ASMEXT = S -SOEXT = @SOEXT@ -DLEXT = @DLEXT@ -MANTYPE = @MANTYPE@ -SYMBOL_PREFIX = @SYMBOL_PREFIX@ - -INSTALLED_LIST= .installed.list - -NEWLINE_C = enc/trans/newline.c -MINIPRELUDE_C = miniprelude.c -PRELUDE_C = prelude.c -GOLF_PRELUDE_C= golf_prelude.c -RBCONFIG = .rbconfig.time - -MAINSRC = $(MAINOBJ:@OBJEXT@=c) - -SRC_FILE = $< -OS_SRC_FILE = $< -DEST_FILE = $@ -OS_DEST_FILE = $@ - -MESSAGE_BEGIN = @for line in -MESSAGE_END = ; do echo "$$line"; done -ECHO_BEGIN = @sep=''; for word in -ECHO_END = ; do echo @ECHO_N@ "$$sep'$$word'@ECHO_C@"; sep=' '; done; echo - -DESTDIR = @DESTDIR@ - -configure_args = @configure_args@ -#### End of variables - -.SUFFIXES: .inc .h .c .y .i .$(ASMEXT) .$(DTRACE_EXT) - -all: - -# Prevent GNU make v3 from overflowing arg limit on SysV. -.NOEXPORT: - -miniruby$(EXEEXT): - @-if test -f $@; then $(MV) -f $@ $@.old; $(RM) $@.old; fi - $(ECHO) linking $@ - $(Q) $(PURIFY) $(CC) $(LDFLAGS) $(XLDFLAGS) $(NORMALMAINOBJ) $(MINIOBJS) $(COMMONOBJS) $(MAINLIBS) $(LIBS) $(OUTFLAG)$@ - $(Q) $(POSTLINK) - -$(PROGRAM): - @$(RM) $@ - $(ECHO) linking $@ - $(Q) $(PURIFY) $(CC) $(LDFLAGS) $(XLDFLAGS) $(MAINOBJ) $(EXTOBJS) $(LIBRUBYARG) $(MAINLIBS) $(LIBS) $(EXTLIBS) $(OUTFLAG)$@ - $(Q) $(POSTLINK) - -PRE_LIBRUBY_UPDATE = [ -n "$(LIBRUBY_SO_UPDATE)" ] || $(exec) $(RM) $(LIBRUBY_EXTS) - -# We must `rm' the library each time this rule is invoked because "updating" a -# MAB library on Apple/NeXT (see --enable-fat-binary in configure) is not -# supported. -$(LIBRUBY_A): - @$(RM) $@ - @-[ -z "$(EXTSTATIC)" ] || $(PRE_LIBRUBY_UPDATE) - $(ECHO) linking static-library $@ - $(Q) $(AR) $(ARFLAGS) $@ $(LIBRUBY_A_OBJS) $(INITOBJS) - @-$(RANLIB) $@ 2> /dev/null || true - -verify-static-library: $(LIBRUBY_A) - $(ECHO) verifying static-library $@ - @$(PURIFY) $(CC) $(LDFLAGS) $(XLDFLAGS) $(MAINOBJ) $(LIBRUBY_A) $(MAINLIBS) $(EXTLIBS) $(LIBS) $(OUTFLAG)conftest$(EXEEXT) - @$(RMALL) conftest$(EXEEXT) conftest.c conftest.dSYM - -$(LIBRUBY_SO): - @-[ -n "$(EXTSTATIC)" ] || $(PRE_LIBRUBY_UPDATE) - $(ECHO) linking shared-library $@ - $(Q) $(LDSHARED) $(DLDFLAGS) $(OBJS) $(DLDOBJS) $(SOLIBS) $(EXTSOLIBS) $(OUTFLAG)$@ - -$(Q) $(OBJCOPY) -w -L '$(SYMBOL_PREFIX)Init_*' -L '$(SYMBOL_PREFIX)ruby_static_id_*' \ - -L '$(SYMBOL_PREFIX)*_threadptr_*' -L '$(SYMBOL_PREFIX)*_ec_*' $@ - $(Q) $(POSTLINK) - @-$(MINIRUBY) -e 'so, *aliases = ARGV; aliases.uniq!; aliases.delete(File.basename(so)); \ - aliases.each { |link| File.delete link rescue nil; File.symlink so, link }' \ - $(LIBRUBY_SO) $(LIBRUBY_ALIASES) || true - -LIBRUBY_WITH_EXT = @LIBRUBY_WITH_EXT@ -$(LIBRUBY_$(LIBRUBY_WITH_EXT)): $(LIBRUBY_SO_UPDATE) - -ruby_pc = @ruby_pc@ -ruby.pc: $(ruby_pc) -$(ruby_pc): config.status - @./config.status --file=$@:$(srcdir)/template/ruby.pc.in - -ruby-runner.h: template/ruby-runner.h.in config.status - @./config.status --file=$@:$(srcdir)/template/$(@F).in - -$(RBCONFIG): $(PREP) - -rbconfig.rb: $(RBCONFIG) - -install-cross: $(arch)-fake.rb $(RBCONFIG) rbconfig.rb $(arch_hdrdir)/ruby/config.h \ - $(LIBRUBY_A) $(LIBRUBY_SO) $(ARCHFILE) - $(ECHO) installing cross-compiling stuff - $(Q) $(MAKEDIRS) $(XRUBY_RUBYLIBDIR)/$(arch) $(XRUBY_RUBYHDRDIR)/$(arch)/ruby - $(Q) sed '/^\$$:\.unshift/q' $(arch)-fake.rb > fake.rb - $(Q) $(BASERUBY) -p \ - -e '~/^\s*CONFIG\["LDFLAGS"\]/ and' \ - -e '$$_[/(?=\s*"$$)/] = %q[ #{(CONFIG["LIBPATHFLAG"]%File.dirname(__FILE__)).strip}]' \ - rbconfig.rb > fake-rbconfig.rb - $(INSTALL_SCRIPT) fake.rb $(XRUBY_RUBYLIBDIR)/$(arch)/fake.rb - $(INSTALL_SCRIPT) fake-rbconfig.rb $(XRUBY_RUBYLIBDIR)/$(arch)/rbconfig.rb - @$(RM) fake.rb fake-rbconfig.rb - $(INSTALL_DATA) $(arch_hdrdir)/ruby/config.h $(XRUBY_RUBYHDRDIR)/$(arch)/ruby - $(INSTALL_DATA) $(top_srcdir)/include/ruby/win32.h $(XRUBY_RUBYHDRDIR)/ruby - $(INSTALL_DATA) $(LIBRUBY) $(LIBRUBY_A) $(XRUBY_RUBYLIBDIR)/$(arch) - $(INSTALL_PROGRAM) $(LIBRUBY_SO) $(XRUBY_RUBYLIBDIR)/$(arch) - -Makefile: $(srcdir)/Makefile.in $(srcdir)/enc/Makefile.in - -$(MKFILES): config.status $(srcdir)/version.h - @[ -f $@ ] && mv $@ $@.old - MAKE=$(MAKE) $(SHELL) ./config.status $@ - @cmp $@ $@.old > /dev/null 2>&1 && echo $@ unchanged && exit 0; \ - { \ - echo "all:; -@rm -f conftest.mk"; \ - echo "conftest.mk: .force; @echo AUTO_REMAKE"; \ - echo ".force:"; \ - } > conftest.mk || exit 1; \ - $(MAKE) -f conftest.mk | grep '^AUTO_REMAKE$$' >/dev/null 2>&1 || \ - { echo "$@ updated, restart."; exit 1; } - -uncommon.mk: $(srcdir)/common.mk - sed 's/{\$$([^(){}]*)[^{}]*}//g' $< > $@ - -.PHONY: reconfig -reconfig-args = $(srcdir)/$(CONFIGURE) $(configure_args) -config.status-args = ./config.status --recheck -reconfig-exec-0 = test -t 1 && { : $${CONFIGURE_TTY=yes}; export CONFIGURE_TTY; }; exec 3>&1; exit `exec 4>&1; { "$$@" 3>&- 4>&-; echo $$? 1>&4; } | fgrep -v '(cached)' 1>&3 3>&- 4>&-` -reconfig-exec-1 = set -x; "$$@" - -reconfig config.status: $(srcdir)/$(CONFIGURE) $(srcdir)/enc/Makefile.in \ - $(srcdir)/include/ruby/version.h - @PWD= MINIRUBY="$(MINIRUBY)"; export MINIRUBY; \ - warnflags="@warnflags@"; export warnflags; \ - set $(SHELL) $($@-args); $(reconfig-exec-$(V)) - -$(srcdir)/$(CONFIGURE): $(srcdir)/configure.ac $(srcdir)/aclocal.m4 - $(CHDIR) $(srcdir) && exec $(AUTOCONF) -o $(@F) - -$(srcdir)/aclocal.m4: - $(CHDIR) $(srcdir) && \ - type $(ACLOCAL) >/dev/null 2>&1 && exec $(ACLOCAL); \ - touch $(@F) - -prereq: $(srcdir)/$(CONFIGURE) - -incs: id.h -all-incs: probes.h - -# Things which should be considered: -# * with gperf v.s. without gperf -# * committers may have various versions of gperf -# * ./configure v.s. ../ruby/configure -# * GNU make v.s. HP-UX make # HP-UX make invokes the action if lex.c and keywords has same mtime. -# * svn checkout generate a file with mtime as current time -# * ext4 and XFS has a mtime with fractional part -lex.c: defs/keywords - @\ - if cmp -s $(srcdir)/defs/lex.c.src $?; then \ - [ $(Q) ] && echo copying $@ || set -x; \ - $(CP) $(srcdir)/lex.c.blt $@; \ - else \ - [ $(Q) ] && echo generating $@ || set -x; \ - gperf -C -P -p -j1 -i 1 -g -o -t -N rb_reserved_word -k1,3,$$ $? \ - | sed -f $(srcdir)/tool/gperf.sed \ - > $@.tmp && \ - $(MV) $@.tmp $@ && \ - $(CP) $? $(srcdir)/defs/lex.c.src && \ - $(CP) $@ $(srcdir)/lex.c.blt; \ - fi - -JIS_PROPS_OPTIONS = -k1,3 -7 -c -j1 -i1 -t -C -P -t --ignore-case -H onig_jis_property_hash -Q onig_jis_property_pool -N onig_jis_property - -$(srcdir)/enc/jis/props.h: enc/jis/props.kwd - $(MAKEDIRS) $(@D) - @set +e; \ - if cmp -s $(?:.kwd=.src) $?; then \ - set -x; \ - $(CP) $(?:.kwd=.h.blt) $@; \ - else \ - set -x; \ - gperf $(JIS_PROPS_OPTIONS) $? | \ - sed -f $(srcdir)/tool/gperf.sed > $@ && \ - $(CP) $? $(?:.kwd=.src) && \ - $(CP) $@ $(?:.kwd=.h.blt); \ - fi - -.c.@OBJEXT@: - @$(ECHO) compiling $< - $(Q) $(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(COUTFLAG)$@ -c $< - -.$(ASMEXT).@OBJEXT@: - @$(ECHO) assembling $< - $(Q) $(CC) $(ASFLAGS) -o $@ -c $< - -.c.$(ASMEXT): - @$(ECHO) translating $< - $(Q) $(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(COUTFLAG)$@ -S $< - -.c.i: - @$(ECHO) preprocessing $< - $(Q) $(CPP) $(warnflags) $(XCFLAGS) $(CPPFLAGS) $(COUTFLAG)$@ -E $< > $@ - -.d.h: - @$(ECHO) translating probes $< - $(Q) $(DTRACE) -o $@.tmp -h -C $(INCFLAGS) -s $< - $(Q) sed -e 's/RUBY_/RUBY_DTRACE_/g' -e 's/PROBES_H_TMP/RUBY_PROBES_H/' -e 's/(char \*/(const char */g' -e 's/, char \*/, const char */g' $@.tmp > $@ - $(Q) $(RM) $@.tmp - -.dmyh.h: - @$(ECHO) making dummy $(DEST_FILE) - $(Q)echo '#include "$(*F).dmyh"' > $@ - -probes.stamp: $(DTRACE_REBUILD_OBJS) - $(Q) if test -f $@ -o -f probes.$(OBJEXT); then \ - $(RM) $(DTRACE_REBUILD_OBJS) $@; \ - $(ECHO0) "rebuilding objects which were modified by \"dtrace -G\""; \ - $(MAKE) $(DTRACE_REBUILD_OBJS); \ - fi - $(Q) touch $@ - -probes.$(OBJEXT): $(srcdir)/probes.d $(DTRACE_REBUILD:yes=probes.stamp) - @$(ECHO) processing probes in object files - $(Q) $(RM) $@ - $(Q) $(DTRACE) -G -C $(INCFLAGS) -s $(srcdir)/probes.d -o $@ $(DTRACE_REBUILD_OBJS) - -main: mjit-headers -yes-mjit-headers: $(MJIT_MIN_HEADER) -clean-local:: - $(Q)$(RM) $(MJIT_HEADER) $(MJIT_HEADER:.h=)$(MJIT_HEADER_SUFFIX:%=*).h - $(Q)$(RM) $(MJIT_MIN_HEADER) $(MJIT_MIN_HEADER:.h=)$(MJIT_HEADER_SUFFIX:%=*).h - $(Q)$(RM) $(MJIT_HEADER_INSTALL_DIR)/rb_mjit_min_header-*.h - $(Q)$(RM) $(TIMESTAMPDIR)/$(MJIT_HEADER:.h=)$(MJIT_HEADER_SUFFIX).time mjit_config.h - $(Q)$(RM) -r mjit_build_dir.* - -$(Q) $(RMDIRS) $(MJIT_HEADER_INSTALL_DIR) 2> $(NULL) || exit 0 - -# DTrace static library hacks described here: -# http://mail.opensolaris.org/pipermail/dtrace-discuss/2005-August/000207.html -ruby-glommed.$(OBJEXT): - @$(ECHO) generating a glommed object with DTrace probes for static library - $(Q) $(LD) -r -o $@ $(OBJS) - -clean-local:: - $(Q)$(RM) ext/extinit.c ext/extinit.$(OBJEXT) ext/ripper/y.output \ - enc/encinit.c enc/encinit.$(OBJEXT) - -$(Q)$(RM) $(pkgconfig_DATA) - -$(Q)$(RMALL) exe/ ruby-runner.$(OBJEXT) ruby-runner.h *.dSYM - -distclean-local:: - $(Q)$(RM) ext/config.cache $(RBCONFIG) Doxyfile - -$(Q)$(RM) run.gdb - -$(Q)$(RM) $(INSTALLED_LIST) $(arch_hdrdir)/ruby/config.h verconf.h - -$(Q)$(RMDIRS) $(arch_hdrdir)/ruby 2> /dev/null || true - -ext/clean.sub gems/clean.sub:: ext/clean.mk -ext/distclean.sub gems/distclean.sub:: ext/distclean.mk -ext/realclean.sub gems/realclean.sub:: ext/realclean.mk - -ext/clean.mk ext/distclean.mk ext/realclean.mk:: - -$(Q) if [ -f $(EXTS_MK) ]; then exec $(MAKE) -f $(EXTS_MK) $(@F:.mk=); fi - -ext/clean:: ext/clean.sub -ext/distclean:: ext/distclean.sub -ext/realclean:: ext/realclean.sub -gems/clean:: gems/clean.sub -gems/distclean:: gems/distclean.sub -gems/realclean:: gems/realclean.sub - -ext/clean.sub ext/distclean.sub ext/realclean.sub \ -gems/clean.sub gems/distclean.sub gems/realclean.sub:: - $(Q) set dummy `echo "${EXTS}" | tr , ' '`; shift; \ - test "$$#" = 0 && set .; \ - set dummy `\ - cd $(@D) 2>/dev/null && \ - find "$$@" \( -name Makefile -o -name exts.mk \) -print | \ - sed -n 's:^\./::;s:^:$(@D)/:;s:/[^/][^/]*$$::p' | sort -u; \ - `; shift; \ - for dir do \ - $(RM) "$$dir/exts.mk"; \ - if [ -f "$$dir/Makefile" ]; then \ - echo $(@F:.sub=)ing "$$dir"; \ - (cd "$$dir" && exec $(MAKE) $(mflags) $(@F:.sub=)); \ - fi; \ - done || true - -ext/distclean ext/realclean gems/distclean gems/realclean:: - $(Q) set dummy `echo "${EXTS}" | tr , ' '`; shift; \ - test "$$#" = 0 && set .; \ - cd $(@D) 2>/dev/null && \ - find "$$@" -type d -empty -exec $(RMDIRS) {} + 2> /dev/null || true - $(Q) $(RMDIRS) $(@D) 2> /dev/null || true - -clean-enc distclean-enc realclean-enc: - @test -f "$(ENC_MK)" || exit 0; \ - echo $(@:-enc=ing) encodings; \ - exec $(MAKE) $(MAKE_ENC) $(@:-enc=) - -ext/extinit.$(OBJEXT): ext/extinit.c $(SETUP) - $(ECHO) compiling $@ - $(Q) $(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(COUTFLAG)$@ -c ext/extinit.c - -enc/encinit.$(OBJEXT): enc/encinit.c $(SETUP) - -cont.$(OBJEXT): $(COROUTINE_H) - -test-bundled-gems-run: - $(Q) set -e; while read gem _; do \ - echo testing $$gem gem && \ - $(XRUBY) -C $(srcdir)/gems/src/$$gem -Ilib ../../../.bundle/bin/rake; \ - done < $(srcdir)/gems/bundled_gems - -update-src:: - @$(CHDIR) "$(srcdir)" && LC_TIME=C exec $(VCSUP) - -update-download:: update-config_files - -after-update:: prereq - -gcov: - $(Q) $(BASERUBY) $(srcdir)/tool/run-gcov.rb - -lcov: - $(Q) $(BASERUBY) $(srcdir)/tool/run-lcov.rb - -update-benchmark-driver: - $(Q) $(srcdir)/tool/git-refresh -C $(srcdir)/benchmark $(Q1:0=-q) \ - --branch $(BENCHMARK_DRIVER_GIT_REF) \ - $(BENCHMARK_DRIVER_GIT_URL) benchmark-driver $(GIT_OPTS) - -update-doclie: - $(Q) $(srcdir)/tool/git-refresh -C $(srcdir)/coverage $(Q1:0=-q) \ - --branch $(DOCLIE_GIT_REF) \ - $(DOCLIE_GIT_URL) doclie $(GIT_OPTS) - -update-simplecov-html: - $(Q) $(srcdir)/tool/git-refresh -C $(srcdir)/coverage $(Q1:0=-q) \ - --branch $(SIMPLECOV_HTML_GIT_REF) \ - $(SIMPLECOV_HTML_GIT_URL) simplecov-html $(GIT_OPTS) - -update-simplecov: - $(Q) $(srcdir)/tool/git-refresh -C $(srcdir)/coverage $(Q1:0=-q) \ - --branch $(SIMPLECOV_GIT_REF) \ - $(SIMPLECOV_GIT_URL) simplecov $(GIT_OPTS) - -update-coverage: update-simplecov update-simplecov-html update-doclie - -update-known-errors: - errno --list | cut -d' ' -f1 | sort -u - $(srcdir)/defs/known_errors.def | \ - $(IFCHANGE) $(srcdir)/defs/known_errors.def - - -INSNS = opt_sc.inc optinsn.inc optunifs.inc insns.inc insns_info.inc \ - vmtc.inc vm.inc mjit_compile.inc - -$(INSNS): $(srcdir)/insns.def vm_opts.h \ - $(srcdir)/defs/opt_operand.def $(srcdir)/defs/opt_insn_unif.def \ - $(srcdir)/tool/insns2vm.rb - $(ECHO) generating $@ - $(Q) $(BASERUBY) -Ku $(srcdir)/tool/insns2vm.rb $(INSNS2VMOPT) $@ - -verconf.h: $(RBCONFIG) - -loadpath: verconf.h - @$(CPP) $(XCFLAGS) $(CPPFLAGS) $(srcdir)/loadpath.c | \ - sed -e '1,/^const char ruby_initial_load_paths/d;/;/,$$d' \ - -e '/^ /!d;s/ *"\\0"$$//;s/" *"//g' - -un-runnable: - $(ECHO) cannot make runnable, configure with --enable-load-relative. - $(Q) exit 1 - -mjit_config.h: - $(ECHO) making $@ - @{ \ - . $(srcdir)/tool/mjit_archflag.sh; \ - parse_arch_flags "$(UNIVERSAL_ARCHNAMES)" $(ARCH_FLAG); \ - test "$(Q)" = @ || set -x; \ - echo '#ifndef RUBY_MJIT_CONFIG_H'; \ - echo '#define RUBY_MJIT_CONFIG_H 1'; \ - echo; \ - sep=; \ - echo '#ifdef LOAD_RELATIVE'; \ - quote MJIT_HEADER_INSTALL_DIR "/$(MJIT_HEADER_INSTALL_DIR)"; \ - echo '#else'; \ - quote MJIT_HEADER_INSTALL_DIR "$(rubyarchhdrdir)"; \ - echo '#endif'; \ - quote MJIT_MIN_HEADER_NAME "$(MJIT_MIN_HEADER_NAME)"; \ - sep=,; \ - quote "MJIT_CC_COMMON " $(MJIT_CC); \ - quote "MJIT_CFLAGS MJIT_ARCHFLAG" $(MJIT_CFLAGS); \ - quote "MJIT_OPTFLAGS " $(MJIT_OPTFLAGS); \ - quote "MJIT_DEBUGFLAGS " $(MJIT_DEBUGFLAGS); \ - quote "MJIT_LDSHARED " $(MJIT_LDSHARED); \ - quote "MJIT_DLDFLAGS MJIT_ARCHFLAG" $(MJIT_DLDFLAGS); \ - quote "MJIT_LIBS " $(LIBRUBYARG_SHARED); \ - quote 'PRELOADENV "@PRELOADENV@"'; \ - indent=$${archs:+' '}; \ - define_arch_flags; \ - echo; \ - echo '#endif /* RUBY_MJIT_CONFIG_H */'; \ - } > $@ - -yes-test-almost yes-test-all: mjit_build_dir.$(SOEXT) -mjit_build_dir.$(SOEXT): $(MJIT_MIN_HEADER) $(srcdir)/ruby-runner.c ruby-runner.h - $(ECHO) making $@ - $(Q) $(DLDSHARED) $(MJIT_DLDFLAGS) $(ARCH_FLAG) $(CFLAGS) $(CPPFLAGS) \ - -DMAKE_MJIT_BUILD_DIR=1 -DMJIT_MIN_HEADER='"$(MJIT_MIN_HEADER)"' \ - $(OUTFLAG)$@ $(srcdir)/ruby-runner.c @@ -1,669 +0,0 @@ -# -*- rdoc -*- - -= NEWS for Ruby 2.6.0 - -This document is a list of user visible feature changes made between -releases except for bug fixes. - -Note that each entry is kept so brief that no reason behind or reference -information is supplied with. For a full list of changes with all -sufficient information, see the ChangeLog file or Redmine -(e.g. <tt>https://bugs.ruby-lang.org/issues/$FEATURE_OR_BUG_NUMBER</tt>) - -== Changes since the 2.5.0 release - -=== Language changes - -* <code>$SAFE</code> now is a process global state and can be set to 0 again. [Feature #14250] - -* Refinements take place at block passing. [Feature #14223] - -* Refinements take place at Kernel#public_send. [Feature #15326] - -* Refinements take place at Kernel#respond_to?. [Feature #15327] - -* +else+ without +rescue+ now causes a syntax error. [EXPERIMENTAL] [Feature #14606] - -* Constant names may start with a non-ASCII capital letter. [Feature #13770] - -* Endless ranges are introduced. You can use a Range that has no end, - like <code>(0..)</code> (or similarly <code>(0...)</code>). [Feature #12912] - - The following shows typical use cases: - - ary[1..] # identical to ary[1..-1] - (1...).each {|index| block } # infinite loop from index 1 - ary.zip(1..) {|elem, index| block } # ary.each.with_index(1) { } - -* Non-Symbol keys in a keyword arguments hash cause an exception. - -* The "shadowing outer local variable" warning is removed. [Feature #12490] - - You can now write the following without warning: - - user = users.find {|user| cond(user) } - -* Print +cause+ of the exception if the exception is not caught and printed - its backtraces and error message. [Feature #8257] - -* The flip-flop syntax is deprecated. [Feature #5400] - -=== Core classes updates (outstanding ones only) - -[Array] - - [New methods] - - * Added Array#union and Array#difference instance methods. [Feature #14097] - - [Modified methods] - - * Array#to_h now accepts a block that maps elements to new key/value pairs. [Feature #15143] - - [Aliased methods] - - * Array#filter is a new alias for Array#select. [Feature #13784] - * Array#filter! is a new alias for Array#select!. [Feature #13784] - -[Binding] - - [New methods] - - * Added Binding#source_location. [Feature #14230] - - This method returns the source location of the binding, a 2-element - array of <code>__FILE__</code> and <code>__LINE__</code>. - Traditionally, the same information could be retrieved by - <code>eval("[__FILE__, __LINE__]", binding)</code>, but we are - planning to change this behavior so that Kernel#eval ignores - binding's source location [Bug #4352]. So, users should use this - newly-introduced method instead of Kernel#eval. - -[Dir] - - [New methods] - - * Added Dir#each_child and Dir#children instance methods. [Feature #13969] - -[Enumerable] - - [New methods] - - * Enumerable#chain returns an enumerator object that iterates over the - elements of the receiver and then those of each argument - in sequence. [Feature #15144] - - [Modified methods] - - * Enumerable#to_h now accepts a block that maps elements to new key/value pairs. [Feature #15143] - - [Aliased methods] - - * Enumerable#filter is a new alias for Enumerable#select. [Feature #13784] - -[Enumerator::ArithmeticSequence] - - * This is a new class to represent a generator of an arithmetic sequence, - that is a number sequence defined by a common difference. It can be used - for representing what is similar to Python's slice. You can get an - instance of this class from Numeric#step and Range#step. - -[Enumerator::Chain] - - * This is a new class to represent a chain of enumerables that works as a - single enumerator, generated by such methods as Enumerable#chain and - Enumerator#+. - -[Enumerator::Lazy] - - [Aliased methods] - - * Enumerator::Lazy#filter is a new alias for - Enumerator::Lazy#select. [Feature #13784] - -[Enumerator] - - [New methods] - - * Enumerator#+ returns an enumerator object that iterates over the - elements of the receiver and then those of the other operand. [Feature #15144] - -[ENV] - - [Modified methods] - - * ENV.to_h now accepts a block that maps names and values to new keys and values. [Feature #15143] - -[Exception] - - [New options] - - * Exception#full_message takes +:highlight+ and +:order+ - options. [Bug #14324] - -[Hash] - - [Modified methods] - - * Hash#merge, Hash#merge!, and Hash#update now accept multiple - arguments. [Feature #15111] - - * Hash#to_h now accepts a block that maps keys and values to new keys and values. [Feature #15143] - - [Aliased methods] - - * Hash#filter is a new alias for Hash#select. [Feature #13784] - - * Hash#filter! is a new alias for Hash#select!. [Feature #13784] - -[IO] - - [New options] - - * Added new mode character <code>'x'</code> to open files for exclusive - access. [Feature #11258] - -[Kernel] - - [Aliased methods] - - * Kernel#then is a new alias for Kernel#yield_self. [Feature #14594] - - [New options] - - * Kernel#Complex, Kernel#Float, Kernel#Integer, and - Kernel#Rational take an +:exception+ option to specify the way of - error handling. [Feature #12732] - - * Kernel#system takes an +:exception+ option to raise an exception - on failure. [Feature #14386] - - [Incompatible changes] - - * Kernel#system and Kernel#exec do not close non-standard file descriptors - (the default of the +:close_others+ option is changed to +false+, - but we still set the +FD_CLOEXEC+ flag on descriptors we - create). [Misc #14907] - -[KeyError] - - [New options] - - * KeyError.new accepts +:receiver+ and +:key+ options to set receiver and - key in Ruby code. [Feature #14313] - -[Method] - - [New methods] - - * Added Method#<< and Method#>> for Proc composition. [Feature #6284] - -[Module] - - [Modified methods] - - * Module#method_defined?, Module#private_method_defined?, and - Module#protected_method_defined? now accept the second - parameter as optional. If it is +true+ (the default value), it checks - ancestor modules/classes, or checks only the class itself. [Feature #14944] - -[NameError] - - [New options] - - * NameError.new accepts a +:receiver+ option to set receiver in Ruby - code. [Feature #14313] - -[NilClass] - - [New methods] - - * NilClass#=~ is added for compatibility. [Feature #15231] - -[NoMethodError] - - [New options] - - * NoMethodError.new accepts a +:receiver+ option to set receiver in Ruby - code. [Feature #14313] - -[Numeric] - - [Incompatible changes] - - * Numeric#step now returns an instance of the Enumerator::ArithmeticSequence - class rather than one of the Enumerator class. - -[OpenStruct] - - [Modified methods] - - * OpenStruct#to_h now accepts a block that maps keys and values to new keys and values. [Feature #15143] - -[Proc] - - [New methods] - - * Added Proc#<< and Proc#>> for Proc composition. [Feature #6284] - - [Incompatible changes] - - * Proc#call doesn't change <code>$SAFE</code> any more. [Feature #14250] - -[Random] - - [New methods] - - * Added Random.bytes. [Feature #4938] - -[Range] - - [New methods] - - * Added Range#% instance method. [Feature #14697] - - [Incompatible changes] - - * Range#=== now uses the +#cover?+ instead of the +#include?+ method. [Feature #14575] - * Range#cover? now accepts a Range object. [Feature #14473] - * Range#step now returns an instance of the Enumerator::ArithmeticSequence - class rather than one of the Enumerator class. - -[Regexp/String] - - * Update Unicode version from 10.0.0 to 11.0.0. [Feature #14802] - - This includes a rewrite of the grapheme cluster (/\X/) algorithm - and special-casing for Georgian MTAVRULI on String#downcase. - - * Update Emoji version from 5.0 to 11.0.0 [Feature #14802] - -[RubyVM::AbstractSyntaxTree] - - [New methods] - - * RubyVM::AbstractSyntaxTree.parse parses a given string and returns AST - nodes. [experimental] - - * RubyVM::AbstractSyntaxTree.parse_file parses a given file and returns AST - nodes. [experimental] - - * RubyVM::AbstractSyntaxTree.of returns AST nodes of the given proc or method. - [experimental] - -[RubyVM] - - [New methods] - - * RubyVM.resolve_feature_path identifies the file that will be loaded by - "require(feature)". [experimental] [Feature #15230] - -[String] - - * String#crypt is now deprecated. [Feature #14915] - - [New features] - - * String#split yields each substring to the block if given. [Feature #4780] - -[Struct] - - [Modified methods] - - * Struct#to_h now accepts a block that maps keys and values to new keys and values. [Feature #15143] - - [Aliased method] - - * Struct#filter is a new alias for Struct#select. [Feature #13784] - -[Time] - - [New features] - - * Time.new and Time#getlocal accept a timezone object as well as - a UTC offset string. Time#+, Time#-, and Time#succ also preserve - the timezone. [Feature #14850] - -[TracePoint] - - [New features] - - * "script_compiled" event is supported. [Feature #15287] - - [New methods] - - * TracePoint#parameters [Feature #14694] - - * TracePoint#instruction_sequence [Feature #15287] - - * TracePoint#eval_script [Feature #15287] - - [Modified methods] - - * TracePoint#enable accepts new keywords "target:" and "target_line:". - [Feature #15289] - -=== Stdlib updates (outstanding ones only) - -[BigDecimal] - - Update to version 1.4.0. This version includes several compatibility - issues, see Compatibility issues section below for details. - - [Modified methods] - - * BigDecimal() accepts the new keyword "exception:" similar to Float(). - - [Note for the differences among recent versions] - - You should want to know the differences among recent versions of bigdecimal. - Please select the suitable version of bigdecimal according to the following - information. - - * 1.3.5 has BigDecimal.new without "exception:" keyword. You can see the - deprecation warning of BigDecimal.new when you specify "-w" option. - BigDecimal(), BigDecimal.new, and Object#to_d methods are the same. - - * 1.4.0 has BigDecimal.new with "exception:" keyword. You always see the - deprecation warning of BigDecimal.new. Object#to_d method is different - from BigDecimal() and BigDecimal.new. - - * 2.0.0 will be released soon after releasing Ruby 2.6.0. This version - will not have the BigDecimal.new method. - -[Bundler] - - * Add Bundler to Standard Library. [Feature #12733] - - * Use 1.17.2, the latest stable version. - -[Coverage] - - A oneshot_lines mode is added. [Feature #15022] - - This mode checks "whether each line was executed at least once or not", - instead of "how many times each line was executed". - A hook for each line is fired at most once, and after it is fired - the hook flag is removed, i.e., it runs with zero overhead. - - [New options] - - * Add +:oneshot_lines+ keyword argument to Coverage.start. - - * Add +:stop+ and +:clear+ keyword arguments to Coverage.result. - If +clear+ is true, it clears the counters to zero. - If +stop+ is true, it disables coverage measurement. - - [New methods] - - * Coverage.line_stub, which is a simple helper function that - creates the "stub" of line coverage from a given source code. - -[CSV] - - * Upgrade to 3.0.2. This includes performance improvements especially - for writing. Writing is about 2 times faster. - See https://github.com/ruby/csv/blob/master/NEWS.md. - -[ERB] - - [New options] - - * Add +:trim_mode+ and +:eoutvar+ keyword arguments to ERB.new. - Now non-keyword arguments other than the first one are softly deprecated - and will be removed when Ruby 2.5 becomes EOL. [Feature #14256] - - * erb command's <tt>-S</tt> option is deprecated, and will be removed - in the next version. - -[FileUtils] - - [New methods] - - * FileUtils#cp_lr. [Feature #4189] - -[Matrix] - - [New methods] - - * Matrix#antisymmetric?, Matrix#skew_symmetric? - - * Matrix#map!, Matrix#collect! [Feature #14151] - - * Matrix#[]= - - * Vector#map!, Vector#collect! - - * Vector#[]= - -[Net] - - [New options] - - * Add +:write_timeout+ keyword argument to Net::HTTP.new. [Feature #13396] - - [New methods] - - * Add Net::HTTP#write_timeout and Net::HTTP#write_timeout=. [Feature #13396] - - [New constant] - - * Add Net::HTTPClientException to deprecate Net::HTTPServerException, - whose name is misleading. [Bug #14688] - -[NKF] - - * Upgrade to nkf v2.1.5 - -[Psych] - - * Upgrade to Psych 3.1.0 - -[RDoc] - - * Become about 2 times faster. - - * Use SOURCE_DATE_EPOCH to generate files. - - * Fix method line number that slipped off. - - * Enable <code>--width</code>, <code>--exclude</code>, - and <code>--line-numbers</code> that were ignored. - - * Add support for blockquote by ">>>" in default markup notation. - - * Add support for "Raises" lines in TomDoc notation. - - * Fix syntax error output. - - * Fix many parsing bugs. - -[REXML] - - * Upgrade to REXML 3.1.9. - See https://github.com/ruby/rexml/blob/master/NEWS.md. - - [Improved some XPath implementations] - - * <code>concat()</code> function: Stringify all arguments before concatenating. - - * <code>string()</code> function: Support context node. - - * <code>string()</code> function: Support processing instruction node. - - * Support <code>"*:#{ELEMENT_NAME}"</code> syntax in XPath 2.0. - - [Fixed some XPath implementations] - - * <code>"//#{ELEMENT_NAME}[#{POSITION}]"</code> case - - * <code>string()</code> function: Fix <code>function(document)</code> - returns nodes that are out of root elements. - - * <code>"/ #{ELEMENT_NAME} "</code> case - - * <code>"/ #{ELEMENT_NAME} [ #{PREDICATE} ]"</code> case - - * <code>"/ #{AXIS}::#{ELEMENT_NAME}"</code> case - - * <code>"#{N}-#{M}"</code> case: One or more white spaces were required - before <code>"-"</code> - - * <code>"/child::node()"</code> case - - * <code>"#{FUNCTION}()/#{PATH}"</code> case - - * <code>"@#{ATTRIBUTE}/parent::"</code> case - - * <code>"name(#{NODE_SET})"</code> case - -[CSV] - - * Upgrade to 3.0.4. - See https://github.com/ruby/csv/blob/master/NEWS.md. - -[RSS] - - [New options] - - * RSS::Parser.parse now accepts options as Hash. +:validate+ , - +:ignore_unknown_element+ , +:parser_class+ options are available. - -[RubyGems] - - * Upgrade to RubyGems 3.0.1 - - * https://blog.rubygems.org/2018/12/19/3.0.0-released.html - - * https://blog.rubygems.org/2018/12/23/3.0.1-released.html - -[Set] - - [Aliased methods] - - * Set#filter! is a new alias for Set#select!. [Feature #13784] - -[URI] - - [New constant] - - * Add URI::File to handle the file URI scheme. [Feature #14035] - -=== Compatibility issues (excluding feature bug fixes) - -[Dir] - - * Dir.glob with <code>'\0'</code>-separated pattern list will be deprecated, - and is now warned. [Feature #14643] - -[File] - - * File.read, File.binread, File.write, File.binwrite, File.foreach, and - File.readlines do not invoke external commands even if the path starts - with the pipe character <code>'|'</code>. [Feature #14245] - -[Object] - - * Object#=~ is deprecated. [Feature #15231] - -=== Stdlib compatibility issues (excluding feature bug fixes) - -* These standard libraries have been promoted to default gems. - - * e2mmap - * forwardable - * irb - * logger - * matrix - * mutex_m - * ostruct - * prime - * rexml - * rss - * shell - * sync - * thwait - * tracer - -[BigDecimal] - - * The following methods are removed. - - * BigDecimal.allocate - * BigDecimal.ver - - * Every BigDecimal object is frozen. [Feature #13984] - - * BigDecimal() parses the given string similar to Float(). - - * String#to_d parses the receiver string similar to String#to_f. - - * BigDecimal.new will be removed in version 2.0. - -[Pathname] - - * Pathname#read, Pathname#binread, Pathname#write, Pathname#binwrite, - Pathname#each_line and Pathname#readlines do not invoke external - commands even if the path starts with the pipe character <code>'|'</code>. - This follows [Feature #14245]. - -=== C API updates - -=== Implementation improvements - -* Speedup Proc#call because we don't need to care about <code>$SAFE</code> - any more. [Feature #14318] - - With +lc_fizzbuzz+ benchmark which uses Proc#call many times we can - measure x1.4 improvements. [Bug #10212] - -* Speedup block.call where +block+ is passed block parameter. [Feature #14330] - - Ruby 2.5 improves block passing performance. [Feature #14045] - - Additionally, Ruby 2.6 improves the performance of passed block calling. - -* Introduce an initial implementation of a JIT (Just-in-time) compiler. [Feature #14235] [experimental] - - * <tt>--jit</tt> command line option is added to enable JIT. <tt>--jit-verbose=1</tt> - is good for inspection. See <tt>ruby --help</tt> for others. - * To generate machine code, this JIT compiler uses the C compiler used for building - the interpreter. Currently GCC, Clang, and Microsoft Visual C++ are supported for it. - * <tt>--disable-mjit-support</tt> option is added to configure. This is added for JIT debugging, - but if you get an error on building a header file for JIT, you can use this option to skip - building it as a workaround. - * rb_waitpid reimplemented on Unix-like platforms to maintain - compatibility with processes created for JIT [Bug #14867] - -* VM generator script renewal; makes the generated VM more optimized. [GH-1779] - -* Thread cache enabled for pthreads platforms (for Thread.new and - Thread.start). [Feature #14757] - -* timer thread is eliminated for platforms with POSIX timers. [Misc #14937] - -* Transient Heap (theap) is supported. [Bug #14858] [Feature #14989] - - theap is a managed heap for short-living memory objects. For example, - making a small and short-living Hash object is x2 faster. With rdoc benchmark, - we measured 6-7% performance improvement. - -* Native implementations (arm32, arm64, ppc64le, win32, win64, x86, amd64) of - coroutines to improve performance of Fiber significantly. [Feature #14739] - -=== Miscellaneous changes - -* On macOS, shared libraries no longer include a full version number of Ruby - in their names. This eliminates the burden of each teeny upgrade on the - platform that users need to rebuild every extension library. - - [Before] - * libruby.2.6.0.dylib - * libruby.2.6.dylib -> libruby.2.6.0.dylib - * libruby.dylib -> libruby.2.6.0.dylib - - [After] - * libruby.2.6.dylib - * libruby.dylib -> libruby.2.6.dylib - -* Extracted misc/*.el files to https://github.com/ruby/elisp diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000000..f6c3c6fc97 --- /dev/null +++ b/NEWS.md @@ -0,0 +1,820 @@ +# NEWS for Ruby 3.2.0 + +This document is a list of user-visible feature changes +since the **3.1.0** release, except for bug fixes. + +Note that each entry is kept to a minimum, see links for details. + +## Language changes + +* Anonymous rest and keyword rest arguments can now be passed as + arguments, instead of just used in method parameters. + [[Feature #18351]] + + ```ruby + def foo(*) + bar(*) + end + def baz(**) + quux(**) + end + ``` + +* 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 + +* 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. + + * 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 + +* `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` + +### Removed methods + +The following deprecated methods are removed. + +* `Dir.exists?` [[Feature #17391]] +* `File.exists?` [[Feature #17391]] +* `Kernel#=~` [[Feature #15231]] +* `Kernel#taint`, `Kernel#untaint`, `Kernel#tainted?` + [[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 + +### Updated C APIs + +The following APIs are updated. + +* PRNG update + + `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. + +### Added C APIs + +* `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. + +### Removed C APIs + +The following deprecated APIs are removed. + +* `rb_cData` variable. +* "taintedness" and "trustedness" functions. [[Feature #16131]] + +## Implementation improvements + +* 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]] + +## JIT + +### 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. + +### MJIT + +* 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 3ecdc9d7d3..93c0131690 100644 --- a/README.ja.md +++ b/README.ja.md @@ -1,6 +1,9 @@ -[](https://travis-ci.org/ruby/ruby) -[](https://ci.appveyor.com/project/ruby/ruby/branch/trunk) -[](https://app.wercker.com/project/byKey/e5e7e1704f62b76525022aa424aef6ef) +[](https://github.com/ruby/ruby/actions?query=workflow%3A"MinGW") +[](https://github.com/ruby/ruby/actions?query=workflow%3A"MJIT") +[](https://github.com/ruby/ruby/actions?query=workflow%3A"Ubuntu") +[](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) # Rubyã¨ã¯ @@ -14,7 +17,7 @@ Rubyã¯ãƒ†ã‚スト処ç†é–¢ä¿‚ã®èƒ½åŠ›ãªã©ã«å„ªã‚Œï¼ŒPerlã¨åŒã˜ãらㄠ* ã‚·ãƒ³ãƒ—ãƒ«ãªæ–‡æ³• * 普通ã®ã‚ªãƒ–ã‚¸ã‚§ã‚¯ãƒˆæŒ‡å‘æ©Ÿèƒ½(クラス,メソッドコールãªã©) -* 特殊ãªã‚ªãƒ–ã‚¸ã‚§ã‚¯ãƒˆæŒ‡å‘æ©Ÿèƒ½(Mixin, 特異メソッドãªã©) +* 特殊ãªã‚ªãƒ–ã‚¸ã‚§ã‚¯ãƒˆæŒ‡å‘æ©Ÿèƒ½(Mixin,特異メソッドãªã©) * 演算åオーãƒãƒ¼ãƒãƒ¼ãƒ‰ * ä¾‹å¤–å‡¦ç†æ©Ÿèƒ½ * イテレータã¨ã‚¯ãƒãƒ¼ã‚¸ãƒ£ @@ -22,7 +25,7 @@ Rubyã¯ãƒ†ã‚スト処ç†é–¢ä¿‚ã®èƒ½åŠ›ãªã©ã«å„ªã‚Œï¼ŒPerlã¨åŒã˜ãらㄠ* ダイナミックãƒãƒ¼ãƒ‡ã‚£ãƒ³ã‚° (アーã‚テクãƒãƒ£ã«ã‚ˆã‚‹) * ç§»æ¤æ€§ãŒé«˜ã„.多ãã®Unix-like/POSIX互æ›ãƒ—ラットフォーム上ã§å‹•ãã ã‘ã§ãªã,Windows, macOS, Haikuãªã©ã®ä¸Šã§ã‚‚å‹•ã cf. - https://github.com/ruby/ruby/blob/trunk/doc/contributing.rdoc#platform-maintainers + https://github.com/ruby/ruby/blob/master/doc/contributing.rdoc#platform-maintainers ## 入手法 @@ -33,21 +36,29 @@ https://www.ruby-lang.org/ja/downloads/ ã‚’å‚ç…§ã—ã¦ãã ã•ã„. -### Subversionã§ +### Git -開発先端ã®ã‚½ãƒ¼ã‚¹ã‚³ãƒ¼ãƒ‰ã¯æ¬¡ã®ã‚³ãƒžãƒ³ãƒ‰ã§å–å¾—ã§ãã¾ã™ï¼Ž +ミラーをGitHubã«å…¬é–‹ã—ã¦ã„ã¾ã™ï¼Ž 以下ã®ã‚³ãƒžãƒ³ãƒ‰ã§ãƒªãƒã‚¸ãƒˆãƒªã‚’å–å¾—ã§ãã¾ã™ï¼Ž - $ svn co https://svn.ruby-lang.org/repos/ruby/trunk/ ruby + $ git clone https://github.com/ruby/ruby.git -ä»–ã«é–‹ç™ºä¸ã®ãƒ–ランãƒã®ä¸€è¦§ã¯æ¬¡ã®ã‚³ãƒžãƒ³ãƒ‰ã§è¦‹ã‚‰ã‚Œã¾ã™ï¼Ž +ä»–ã®ãƒ–ランãƒã®ä¸€è¦§ã¯æ¬¡ã®ã‚³ãƒžãƒ³ãƒ‰ã§è¦‹ã‚‰ã‚Œã¾ã™ï¼Ž - $ svn ls https://svn.ruby-lang.org/repos/ruby/branches/ + $ git ls-remote https://github.com/ruby/ruby.git -### Gitã§ +Rubyリãƒã‚¸ãƒˆãƒªã®æœ¬æ¥ã®master㯠https://git.ruby-lang.org/ruby.git ã«ã‚りã¾ã™ï¼Ž +コミッタã¯ã“ã¡ã‚‰ã‚’使ã„ã¾ã™ï¼Ž -Subversionã®ãƒŸãƒ©ãƒ¼ã‚’GitHubã«å…¬é–‹ã—ã¦ã„ã¾ã™ï¼Ž 以下ã®ã‚³ãƒžãƒ³ãƒ‰ã§ãƒªãƒã‚¸ãƒˆãƒªã‚’å–å¾—ã§ãã¾ã™ï¼Ž +### Subversion + +å¤ã„Rubyã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®ã‚½ãƒ¼ã‚¹ã‚³ãƒ¼ãƒ‰ã¯æ¬¡ã®ã‚³ãƒžãƒ³ãƒ‰ã§ã‚‚å–å¾—ã§ãã¾ã™ï¼Ž + + $ svn co https://svn.ruby-lang.org/repos/ruby/branches/ruby_2_6/ ruby + +ä»–ã®ãƒ–ランãƒã®ä¸€è¦§ã¯æ¬¡ã®ã‚³ãƒžãƒ³ãƒ‰ã§è¦‹ã‚‰ã‚Œã¾ã™ï¼Ž + + $ svn ls https://svn.ruby-lang.org/repos/ruby/branches/ - $ git clone https://github.com/ruby/ruby.git ## ホームページ @@ -59,38 +70,33 @@ https://www.ruby-lang.org/ ## メーリングリスト -Rubyã®ãƒ¡ãƒ¼ãƒªãƒ³ã‚°ãƒªã‚¹ãƒˆãŒã‚りã¾ã™ï¼Žå‚åŠ å¸Œæœ›ã®æ–¹ã¯ - -mailto:ruby-list-request@ruby-lang.org - -ã¾ã§æœ¬æ–‡ã« +Rubyã®ãƒ¡ãƒ¼ãƒªãƒ³ã‚°ãƒªã‚¹ãƒˆãŒã‚りã¾ã™ï¼Žå‚åŠ å¸Œæœ›ã®æ–¹ã¯ [ruby-list-request@ruby-lang.org] ã¾ã§æœ¬æ–‡ã« subscribe ã¨æ›¸ã„ã¦é€ã£ã¦ä¸‹ã•ã„. -Ruby開発者å‘ã‘メーリングリストもã‚りã¾ã™ï¼Žã“ã¡ã‚‰ã§ã¯rubyã®ãƒã‚°ï¼Œå°†æ¥ã®ä»•様拡張ãªã©å®Ÿè£…上ã®å•題ã«ã¤ã„ã¦è°è«–ã•れã¦ã„ã¾ã™ï¼Ž å‚åŠ å¸Œæœ›ã®æ–¹ã¯ - -mailto:ruby-dev-request@ruby-lang.org - -ã¾ã§ruby-listã¨åŒæ§˜ã®æ–¹æ³•ã§ãƒ¡ãƒ¼ãƒ«ã—ã¦ãã ã•ã„. +Ruby開発者å‘ã‘メーリングリストもã‚りã¾ã™ï¼Žã“ã¡ã‚‰ã§ã¯rubyã®ãƒã‚°ï¼Œå°†æ¥ã®ä»•様拡張ãªã©å®Ÿè£…上ã®å•題ã«ã¤ã„ã¦è°è«–ã•れã¦ã„ã¾ã™ï¼Ž +å‚åŠ å¸Œæœ›ã®æ–¹ã¯ [ruby-dev-request@ruby-lang.org] ã¾ã§ruby-listã¨åŒæ§˜ã®æ–¹æ³•ã§ãƒ¡ãƒ¼ãƒ«ã—ã¦ãã ã•ã„. Ruby拡張モジュールã«ã¤ã„ã¦è©±ã—åˆã†ruby-extãƒ¡ãƒ¼ãƒªãƒ³ã‚°ãƒªã‚¹ãƒˆã¨æ•°å¦é–¢ä¿‚ã®è©±é¡Œã«ã¤ã„ã¦è©±ã—åˆã†ruby-mathメーリングリスト㨠英語ã§rubyã«ã¤ã„ã¦è©±ã—åˆã†ruby-talkメーリングリストもã‚りã¾ã™ï¼Žå‚åŠ æ–¹æ³•ã¯ã©ã‚Œã‚‚åŒã˜ã§ã™ï¼Ž +[ruby-list-request@ruby-lang.org]: mailto:ruby-list-request@ruby-lang.org?subject=Join%20Ruby%20Mailing%20List&body=subscribe +[ruby-dev-request@ruby-lang.org]: mailto:ruby-dev-request@ruby-lang.org?subject=Join%20Ruby%20Mailing%20List&body=subscribe + ## コンパイル・インストール ä»¥ä¸‹ã®æ‰‹é †ã§è¡Œã£ã¦ãã ã•ã„. -1. ã‚‚ã— `configure` ファイルãŒè¦‹ã¤ã‹ã‚‰ãªã„,もã—ã㯠`configure.ac` よりå¤ã„よã†ãªã‚‰ï¼Œ `autoconf` を実行ã—㦠- æ–°ã—ã `configure` を生æˆã™ã‚‹ +1. (Gitリãƒã‚¸ãƒˆãƒªã‹ã‚‰å–å¾—ã—ãŸã‚½ãƒ¼ã‚¹ã‚’ビルドã™ã‚‹å ´åˆ) `./autogen.sh` を実行ã—ã¦æ–°ã—ã `configure` を生æˆã™ã‚‹ 2. `configure` を実行ã—㦠`Makefile` ãªã©ã‚’生æˆã™ã‚‹ 環境ã«ã‚ˆã£ã¦ã¯ãƒ‡ãƒ•ォルトã®Cコンパイラ用オプションãŒä»˜ãã¾ã™ï¼Ž `configure` オプション㧠`optflags=..` `warnflags=..` ç‰ã§ä¸Šæ›¸ãã§ãã¾ã™ï¼Ž -3. (å¿…è¦ãªã‚‰ã°)`defines.h` を編集ã™ã‚‹ +3. (å¿…è¦ãªã‚‰ã°)`include/ruby/defines.h` を編集ã™ã‚‹ 多分,必è¦ç„¡ã„ã¨æ€ã„ã¾ã™ï¼Ž @@ -99,7 +105,8 @@ Ruby拡張モジュールã«ã¤ã„ã¦è©±ã—åˆã†ruby-extメーリングリスム`ext/Setup` ã«è¨˜è¿°ã—ãŸãƒ¢ã‚¸ãƒ¥ãƒ¼ãƒ«ã¯é™çš„ã«ãƒªãƒ³ã‚¯ã•れã¾ã™ï¼Ž ダイナミックãƒãƒ¼ãƒ‡ã‚£ãƒ³ã‚°ã‚’サãƒãƒ¼ãƒˆã—ã¦ã„ãªã„アーã‚テクãƒãƒ£ã§ã¯ `Setup` ã®1行目ã®ã€Œ`option nodynamic`ã€ã¨ã„ã†è¡Œã®ã‚³ - メントを外ã™å¿…è¦ãŒã‚りã¾ã™ï¼Žã¾ãŸï¼Œã“ã®ã‚¢ãƒ¼ã‚テクãƒãƒ£ã§æ‹¡å¼µãƒ¢ã‚¸ãƒ¥ãƒ¼ãƒ«ã‚’利用ã™ã‚‹ãŸã‚ã«ã¯ï¼Œã‚らã‹ã˜ã‚é™çš„ã«ãƒªãƒ³ã‚¯ã‚’ã—ã¦ãŠãå¿…è¦ãŒã‚りã¾ã™ï¼Ž + メントを外ã™å¿…è¦ãŒã‚りã¾ã™ï¼Ž + ã¾ãŸï¼Œã“ã®ã‚¢ãƒ¼ã‚テクãƒãƒ£ã§æ‹¡å¼µãƒ¢ã‚¸ãƒ¥ãƒ¼ãƒ«ã‚’利用ã™ã‚‹ãŸã‚ã«ã¯ï¼Œã‚らã‹ã˜ã‚é™çš„ã«ãƒªãƒ³ã‚¯ã‚’ã—ã¦ãŠãå¿…è¦ãŒã‚りã¾ã™ï¼Ž 5. `make` を実行ã—ã¦ã‚³ãƒ³ãƒ‘イルã™ã‚‹ @@ -159,17 +166,17 @@ UNIXã§ã‚れ㰠`configure` ãŒã»ã¨ã‚“ã©ã®å·®ç•°ã‚’å¸åŽã—ã¦ãれる㯠## フィードãƒãƒƒã‚¯ -Rubyã«é–¢ã™ã‚‹è³ªå•㯠Ruby-Talk(英語)や Ruby-List(日本語) (https://www.ruby-lang.org/ja/community/mailing-lists) や, -stackoverflow (https://ja.stackoverflow.com/) ãªã©ã®Webã‚µã‚¤ãƒˆã«æŠ•ç¨¿ã—ã¦ãã ã•ã„. +Rubyã«é–¢ã™ã‚‹è³ªå•㯠[Ruby-Talk](英語)や [Ruby-List](日本語)や, +[stackoverflow] ãªã©ã®Webã‚µã‚¤ãƒˆã«æŠ•ç¨¿ã—ã¦ãã ã•ã„. ãƒã‚°å ±å‘Šã¯ https://bugs.ruby-lang.org ã§å—ã‘付ã‘ã¦ã„ã¾ã™ï¼Ž +[Ruby-Talk]: https://www.ruby-lang.org/en/community/mailing-lists +[Ruby-List]: https://www.ruby-lang.org/ja/community/mailing-lists +[stackoverflow]: https://ja.stackoverflow.com/ ## 著者 Rubyã®ã‚ªãƒªã‚¸ãƒŠãƒ«ç‰ˆã¯ï¼Œ1995å¹´ã«ã¾ã¤ã‚‚ã¨ã‚†ãã²ã‚æ°ã«ã‚ˆã£ã¦è¨è¨ˆãƒ»é–‹ç™ºã•れã¾ã—ãŸï¼Ž <mailto:matz@ruby-lang.org> - ---- -created at: Thu Aug 3 11:57:36 JST 1995 @@ -1,172 +1,91 @@ -[](https://travis-ci.org/ruby/ruby) -[](https://ci.appveyor.com/project/ruby/ruby/branch/trunk) -[](https://app.wercker.com/project/byKey/e5e7e1704f62b76525022aa424aef6ef) +[](https://github.com/ruby/ruby/actions?query=workflow%3A"MinGW") +[](https://github.com/ruby/ruby/actions?query=workflow%3A"MJIT") +[](https://github.com/ruby/ruby/actions?query=workflow%3A"Ubuntu") +[](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) -# What's Ruby +# What is Ruby? -Ruby is the interpreted scripting language for quick and easy object-oriented -programming. It has many features to process text files and to do system -management tasks (as in Perl). It is simple, straight-forward, and -extensible. +Ruby is an interpreted object-oriented programming language often +used for web development. It also offers many scripting features +to process plain text and serialized files, or manage system tasks. +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, Haiku, etc.) cf. - https://github.com/ruby/ruby/blob/trunk/doc/contributing.rdoc#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/ -The trunk of the Ruby source tree can be checked out with the following -command: - - $ svn co https://svn.ruby-lang.org/repos/ruby/trunk/ ruby - -Or if you are using git then use the following command: +The mirror of the Ruby source tree can be checked out with the following command: $ git clone https://github.com/ruby/ruby.git There are some other branches under development. Try the following command to see the list of branches: - $ svn ls https://svn.ruby-lang.org/repos/ruby/branches/ + $ git ls-remote https://github.com/ruby/ruby.git -Or if you are using git then use the following command: +You may also want to use https://git.ruby-lang.org/ruby.git (actual master of Ruby source) +if you are a committer. - $ git ls-remote https://github.com/ruby/ruby.git +## How to build -## Ruby home page +see [Building Ruby](doc/contributing/building_ruby.md) -The URL of the Ruby home page is: +## 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 talk about Ruby. To subscribe to this list, please +There is a mailing list to discuss Ruby. To subscribe to this list, please send the following phrase: subscribe -in the mail body (not subject) to the address -<ruby-talk-request@ruby-lang.org>. - -## How to compile and install - -This is what you need to do to compile and install Ruby: - -1. If you want to use Microsoft Visual C++ to compile Ruby, read - [win32/README.win32](win32/README.win32) instead of this document. - -2. If `./configure` does not exist or is older than `configure.ac`, run - `autoconf` to (re)generate configure. - -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 `defines.h` if you need. Usually this step will not be needed. - -5. Remove comment mark(`#`) before the module names from `ext/Setup` (or add - module names if not present), if you want to link modules statically. - - If you don't want to compile non static extension modules (probably on - architectures which do not allow dynamic loading), remove comment mark - from the line "`#option nodynamic`" in `ext/Setup`. +in the mail body (not subject) to the address [ruby-talk-request@ruby-lang.org]. - 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. Optionally, run `make update-gems` and `make extract-gems`. - - If you want to install bundled gems, run `make update-gems` and - `make extract-gems` before running `make install`. - -9. 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. +[ruby-talk-request@ruby-lang.org]: mailto:ruby-talk-request@ruby-lang.org?subject=Join%20Ruby%20Mailing%20List&body=subscribe ## Copying -See the file [COPYING](COPYING). +See the file [COPYING](rdoc-ref:COPYING). ## Feedback -Questions about the Ruby language can be asked on the Ruby-Talk mailing list -(https://www.ruby-lang.org/en/community/mailing-lists) or on websites like -(https://stackoverflow.com). +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. -Bug reports should be filed at https://bugs.ruby-lang.org. Read [HowToReport] for more information. - -[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](CONTRIBUTING.md) - +See ["Contributing to Ruby"](https://docs.ruby-lang.org/en/master/contributing_md.html), which includes setup and build instructions. ## The Author -Ruby was originally designed and developed by Yukihiro Matsumoto (Matz) in -1995. +Ruby was originally designed and developed by Yukihiro Matsumoto (Matz) in 1995. <matz@ruby-lang.org> diff --git a/aclocal.m4 b/aclocal.m4 index b0fe3eb959..e69de29bb2 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -1,46 +0,0 @@ -# generated automatically by aclocal 1.16.1 -*- Autoconf -*- - -# Copyright (C) 1996-2018 Free Software Foundation, Inc. - -# This file is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY, to the extent permitted by law; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A -# PARTICULAR PURPOSE. - -m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])]) -m4_include([tool/m4/_colorize_result_prepare.m4]) -m4_include([tool/m4/ac_msg_result.m4]) -m4_include([tool/m4/colorize_result.m4]) -m4_include([tool/m4/ruby_append_option.m4]) -m4_include([tool/m4/ruby_append_options.m4]) -m4_include([tool/m4/ruby_check_builtin_func.m4]) -m4_include([tool/m4/ruby_check_builtin_setjmp.m4]) -m4_include([tool/m4/ruby_check_printf_prefix.m4]) -m4_include([tool/m4/ruby_check_setjmp.m4]) -m4_include([tool/m4/ruby_check_signedness.m4]) -m4_include([tool/m4/ruby_check_sizeof.m4]) -m4_include([tool/m4/ruby_check_sysconf.m4]) -m4_include([tool/m4/ruby_cppoutfile.m4]) -m4_include([tool/m4/ruby_decl_attribute.m4]) -m4_include([tool/m4/ruby_default_arch.m4]) -m4_include([tool/m4/ruby_define_if.m4]) -m4_include([tool/m4/ruby_defint.m4]) -m4_include([tool/m4/ruby_dtrace_available.m4]) -m4_include([tool/m4/ruby_dtrace_postprocess.m4]) -m4_include([tool/m4/ruby_func_attribute.m4]) -m4_include([tool/m4/ruby_mingw32.m4]) -m4_include([tool/m4/ruby_prepend_option.m4]) -m4_include([tool/m4/ruby_prog_gnu_ld.m4]) -m4_include([tool/m4/ruby_replace_type.m4]) -m4_include([tool/m4/ruby_rm_recursive.m4]) -m4_include([tool/m4/ruby_setjmp_type.m4]) -m4_include([tool/m4/ruby_stack_grow_direction.m4]) -m4_include([tool/m4/ruby_try_cflags.m4]) -m4_include([tool/m4/ruby_try_ldflags.m4]) -m4_include([tool/m4/ruby_type_attribute.m4]) -m4_include([tool/m4/ruby_universal_arch.m4]) -m4_include([tool/m4/ruby_werror_flag.m4]) diff --git a/addr2line.c b/addr2line.c index b2c31b39ad..e5f25293e2 100644 --- a/addr2line.c +++ b/addr2line.c @@ -9,13 +9,11 @@ **********************************************************************/ #if defined(__clang__) -#pragma clang diagnostic ignored "-Wpedantic" +#pragma clang diagnostic ignored "-Wgnu-empty-initializer" #pragma clang diagnostic ignored "-Wgcc-compat" -#elif defined(__GNUC__) -#pragma GCC diagnostic ignored "-Wpedantic" #endif -#include "ruby/config.h" +#include "ruby/internal/config.h" #include "ruby/defines.h" #include "ruby/missing.h" #include "addr2line.h" @@ -23,12 +21,12 @@ #include <stdio.h> #include <errno.h> -#ifdef HAVE_STDBOOL_H -#include <stdbool.h> -#else -#include "missing/stdbool.h" +#ifdef HAVE_LIBPROC_H +#include <libproc.h> #endif +#include "ruby/internal/stdbool.h" + #if defined(USE_ELF) || defined(HAVE_MACH_O_LOADER_H) #include <fcntl.h> @@ -66,8 +64,8 @@ void *alloca(); #endif #ifdef HAVE_MACH_O_LOADER_H +# include <crt_externs.h> # include <mach-o/fat.h> -# include <mach-o/ldsyms.h> # include <mach-o/loader.h> # include <mach-o/nlist.h> # include <mach-o/stab.h> @@ -161,11 +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 5 +#define DWARF_SECTION_COUNT 9 static struct dwarf_section * obj_dwarf_section_at(obj_info_t *obj, int n) @@ -175,7 +177,11 @@ obj_dwarf_section_at(obj_info_t *obj, int n) &obj->debug_info, &obj->debug_line, &obj->debug_ranges, - &obj->debug_str + &obj->debug_str_offsets, + &obj->debug_addr, + &obj->debug_rnglists, + &obj->debug_str, + &obj->debug_line_str }; if (n < 0 || DWARF_SECTION_COUNT <= n) { abort(); @@ -189,15 +195,15 @@ struct debug_section_definition { }; /* Avoid consuming stack as this module may be used from signal handler */ -static char binary_filename[PATH_MAX]; +static char binary_filename[PATH_MAX + 1]; static unsigned long -uleb128(char **p) +uleb128(const char **p) { unsigned long r = 0; int s = 0; for (;;) { - unsigned char b = *(unsigned char *)(*p)++; + unsigned char b = (unsigned char)*(*p)++; if (b < 0x80) { r += (unsigned long)b << s; break; @@ -209,12 +215,12 @@ uleb128(char **p) } static long -sleb128(char **p) +sleb128(const char **p) { long r = 0; int s = 0; for (;;) { - unsigned char b = *(unsigned char *)(*p)++; + unsigned char b = (unsigned char)*(*p)++; if (b < 0x80) { if (b & 0x40) { r -= (0x80 - b) << s; @@ -231,7 +237,7 @@ sleb128(char **p) } static const char * -get_nth_dirname(unsigned long dir, char *p) +get_nth_dirname(unsigned long dir, const char *p) { if (!dir--) { return ""; @@ -248,39 +254,51 @@ get_nth_dirname(unsigned long dir, 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, char *include_directories, 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; - char *p = filenames; - char *filename; + 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, - char *include_directories, 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; @@ -374,7 +405,7 @@ parse_debug_line_header(const char **pp, struct LineNumberProgramHeader *header) } static int -parse_debug_line_cu(int num_traces, void **traces, char **debug_line, +parse_debug_line_cu(int num_traces, void **traces, const char **debug_line, obj_info_t *obj, line_info_t *lines, int offset) { const char *p = (const char *)*debug_line; @@ -392,15 +423,17 @@ parse_debug_line_cu(int num_traces, void **traces, 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, \ - (char *)header.include_directories, \ - (char *)header.filenames, \ + header.format, \ + header.version, \ + header.include_directories, \ + header.filenames, \ obj, lines, offset); \ /*basic_block = prologue_end = epilogue_begin = 0;*/ \ } while (0) @@ -413,19 +446,19 @@ parse_debug_line_cu(int num_traces, void **traces, char **debug_line, FILL_LINE(); break; case DW_LNS_advance_pc: - a = uleb128((char **)&p); + a = uleb128(&p) * header.minimum_instruction_length; addr += a; break; case DW_LNS_advance_line: { - long a = sleb128((char **)&p); + long a = sleb128(&p); line += a; break; } case DW_LNS_set_file: - file = (unsigned int)uleb128((char **)&p); + file = (unsigned int)uleb128(&p); break; case DW_LNS_set_column: - /*column = (unsigned int)*/(void)uleb128((char **)&p); + /*column = (unsigned int)*/(void)uleb128(&p); break; case DW_LNS_negate_stmt: is_stmt = !is_stmt; @@ -434,12 +467,13 @@ parse_debug_line_cu(int num_traces, void **traces, char **debug_line, /*basic_block = 1; */ break; case DW_LNS_const_add_pc: - a = ((255 - header.opcode_base) / header.line_range) * + a = ((255UL - header.opcode_base) / header.line_range) * header.minimum_instruction_length; addr += a; break; case DW_LNS_fixed_advance_pc: - a = *(unsigned char *)p++; + a = *(uint16_t *)p; + p += sizeof(uint16_t); addr += a; break; case DW_LNS_set_prologue_end: @@ -449,10 +483,10 @@ parse_debug_line_cu(int num_traces, void **traces, char **debug_line, /* epilogue_begin = 1; */ break; case DW_LNS_set_isa: - /* isa = (unsigned int)*/(void)uleb128((char **)&p); + /* isa = (unsigned int)*/(void)uleb128(&p); break; case 0: - a = *(unsigned char *)p++; + a = uleb128(&p); op = *p++; switch (op) { case DW_LNE_end_sequence: @@ -476,7 +510,7 @@ parse_debug_line_cu(int num_traces, void **traces, char **debug_line, break; case DW_LNE_set_discriminator: /* TODO:currently ignore */ - uleb128((char **)&p); + uleb128(&p); break; default: kprintf("Unknown extended opcode: %d in %s\n", @@ -499,10 +533,10 @@ parse_debug_line_cu(int num_traces, void **traces, char **debug_line, static int parse_debug_line(int num_traces, void **traces, - char *debug_line, unsigned long size, + const char *debug_line, unsigned long size, obj_info_t *obj, line_info_t *lines, int offset) { - char *debug_line_end = debug_line + size; + const char *debug_line_end = debug_line + size; while (debug_line < debug_line_end) { if (parse_debug_line_cu(num_traces, traces, &debug_line, obj, lines, offset)) return -1; @@ -528,13 +562,25 @@ append_obj(obj_info_t **objp) } #ifdef USE_ELF +/* Ideally we should check 4 paths to follow gnu_debuglink: + * + * - /usr/lib/debug/.build-id/ab/cdef1234.debug + * - /usr/bin/ruby.debug + * - /usr/bin/.debug/ruby.debug + * - /usr/lib/debug/usr/bin/ruby.debug. + * + * but we handle only two cases for now as the two formats are + * used by some linux distributions. + * + * See GDB's info for detail. + * https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html + */ + +// check the path pattern of "/usr/lib/debug/usr/bin/ruby.debug" static void follow_debuglink(const char *debuglink, int num_traces, void **traces, obj_info_t **objp, line_info_t *lines, int offset) { - /* Ideally we should check 4 paths to follow gnu_debuglink, - but we handle only one case for now as this format is used - by some linux distributions. See GDB's info for detail. */ static const char global_debug_dir[] = "/usr/lib/debug"; const size_t global_debug_dir_len = sizeof(global_debug_dir) - 1; char *p; @@ -561,6 +607,37 @@ follow_debuglink(const char *debuglink, int num_traces, void **traces, o2->path = o1->path; fill_lines(num_traces, traces, 0, objp, lines, offset); } + +// check the path pattern of "/usr/lib/debug/.build-id/ab/cdef1234.debug" +static void +follow_debuglink_build_id(const char *build_id, size_t build_id_size, int num_traces, void **traces, + obj_info_t **objp, line_info_t *lines, int offset) +{ + static const char global_debug_dir[] = "/usr/lib/debug/.build-id/"; + const size_t global_debug_dir_len = sizeof(global_debug_dir) - 1; + char *p; + obj_info_t *o1 = *objp, *o2; + size_t i; + + if (PATH_MAX < global_debug_dir_len + 1 + build_id_size * 2 + 6) return; + + memcpy(binary_filename, global_debug_dir, global_debug_dir_len); + p = binary_filename + global_debug_dir_len; + for (i = 0; i < build_id_size; i++) { + static const char tbl[] = "0123456789abcdef"; + unsigned char n = build_id[i]; + *p++ = tbl[n / 16]; + *p++ = tbl[n % 16]; + if (i == 0) *p++ = '/'; + } + strcpy(p, ".debug"); + + append_obj(objp); + o2 = *objp; + o2->base_addr = o1->base_addr; + o2->path = o1->path; + fill_lines(num_traces, traces, 0, objp, lines, offset); +} #endif enum @@ -766,32 +843,51 @@ enum DW_FORM_addrx4 = 0x2c }; +/* Range list entry encodings */ +enum { + DW_RLE_end_of_list = 0x00, + DW_RLE_base_addressx = 0x01, + DW_RLE_startx_endx = 0x02, + DW_RLE_startx_length = 0x03, + DW_RLE_offset_pair = 0x04, + DW_RLE_base_address = 0x05, + DW_RLE_start_end = 0x06, + DW_RLE_start_length = 0x07 +}; + enum { VAL_none = 0, 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; - char *file; - char *current_cu; + const char *file; + uint8_t current_version; + const char *current_cu; uint64_t current_low_pc; - char *debug_line_cu_end; - char *debug_line_files; - char *debug_line_directories; - char *p; - char *cu_end; - char *pend; - char *q0; - char *q; - int format; /* 4 or 8 */; + 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; + const char *cu_end; + const char *pend; + const char *q0; + const char *q; + int format; // 4 or 8 uint8_t address_size; int level; - char *abbrev_table[ABBREV_TABLE_SIZE]; + const char *abbrev_table[ABBREV_TABLE_SIZE]; } DebugInfoReader; typedef struct { @@ -802,9 +898,10 @@ typedef struct { typedef struct { union { - char *ptr; + const char *ptr; uint64_t uint64; int64_t int64; + uint64_t addr_idx; } as; uint64_t off; uint64_t at; @@ -813,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) @@ -835,39 +935,39 @@ get_uint64(const uint8_t *p) } static uint8_t -read_uint8(char **ptr) +read_uint8(const char **ptr) { - const unsigned char *p = (const unsigned char *)*ptr; - *ptr = (char *)(p + 1); - return *p; + const char *p = *ptr; + *ptr = (p + 1); + return (uint8_t)*p; } static uint16_t -read_uint16(char **ptr) +read_uint16(const char **ptr) { - const unsigned char *p = (const unsigned char *)*ptr; - *ptr = (char *)(p + 2); - return get_uint16(p); + const char *p = *ptr; + *ptr = (p + 2); + return get_uint16((const uint8_t *)p); } static uint32_t -read_uint24(char **ptr) +read_uint24(const char **ptr) { - const unsigned char *p = (const unsigned char *)*ptr; - *ptr = (char *)(p + 3); - return (*p << 16) | get_uint16(p+1); + const char *p = *ptr; + *ptr = (p + 3); + return ((uint8_t)*p << 16) | get_uint16((const uint8_t *)p+1); } static uint32_t -read_uint32(char **ptr) +read_uint32(const char **ptr) { - const unsigned char *p = (const unsigned char *)*ptr; - *ptr = (char *)(p + 4); - return get_uint32(p); + const char *p = *ptr; + *ptr = (p + 4); + return get_uint32((const uint8_t *)p); } static uint64_t -read_uint64(char **ptr) +read_uint64(const char **ptr) { const unsigned char *p = (const unsigned char *)*ptr; *ptr = (char *)(p + 8); @@ -875,7 +975,7 @@ read_uint64(char **ptr) } static uintptr_t -read_uintptr(char **ptr) +read_uintptr(const char **ptr) { const unsigned char *p = (const unsigned char *)*ptr; *ptr = (char *)(p + SIZEOF_VOIDP); @@ -916,13 +1016,34 @@ debug_info_reader_init(DebugInfoReader *reader, obj_info_t *obj) reader->p = obj->debug_info.ptr; 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 +di_skip_die_attributes(const char **p) +{ + for (;;) { + uint64_t at = uleb128(p); + uint64_t form = uleb128(p); + if (!at && !form) break; + switch (form) { + default: + break; + case DW_FORM_implicit_const: + sleb128(p); + break; + } + } } static void di_read_debug_abbrev_cu(DebugInfoReader *reader) { uint64_t prev = 0; - char *p = reader->q0; + const char *p = reader->q0; for (;;) { uint64_t abbrev_number = uleb128(&p); if (abbrev_number <= prev) break; @@ -932,12 +1053,7 @@ di_read_debug_abbrev_cu(DebugInfoReader *reader) prev = abbrev_number; uleb128(&p); /* tag */ p++; /* has_children */ - /* skip content */ - for (;;) { - uint64_t at = uleb128(&p); - uint64_t form = uleb128(&p); - if (!at && !form) break; - } + di_skip_die_attributes(&p); } } @@ -948,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; @@ -959,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; @@ -973,7 +1098,7 @@ set_int_value(DebugInfoValue *v, int64_t n) } static void -set_cstr_value(DebugInfoValue *v, char *s) +set_cstr_value(DebugInfoValue *v, const char *s) { v->as.ptr = s; v->off = 0; @@ -981,7 +1106,7 @@ set_cstr_value(DebugInfoValue *v, char *s) } static void -set_cstrp_value(DebugInfoValue *v, char *s, uint64_t off) +set_cstrp_value(DebugInfoValue *v, const char *s, uint64_t off) { v->as.ptr = s; v->off = off; @@ -989,7 +1114,7 @@ set_cstrp_value(DebugInfoValue *v, char *s, uint64_t off) } static void -set_data_value(DebugInfoValue *v, char *s) +set_data_value(DebugInfoValue *v, const char *s) { v->as.ptr = s; v->type = VAL_data; @@ -1005,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); @@ -1069,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->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)); + 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 address_size:%d", reader->address_size); - 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: @@ -1094,7 +1245,7 @@ debug_info_reader_read_value(DebugInfoReader *reader, uint64_t form, DebugInfoVa set_uint_value(v, uleb128(&reader->p)); break; case DW_FORM_indirect: - /* TODO: read the refered value */ + /* TODO: read the referred value */ set_uint_value(v, uleb128(&reader->p)); break; case DW_FORM_sec_offset: @@ -1117,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)); @@ -1136,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)); @@ -1155,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; @@ -1190,11 +1339,10 @@ debug_info_reader_read_value(DebugInfoReader *reader, uint64_t form, DebugInfoVa } /* find abbrev in current compilation unit */ -static char * +static const char * di_find_abbrev(DebugInfoReader *reader, uint64_t abbrev_number) { - uint64_t n; - char *p; + const char *p; if (abbrev_number < ABBREV_TABLE_SIZE) { return reader->abbrev_table[abbrev_number]; } @@ -1202,25 +1350,15 @@ di_find_abbrev(DebugInfoReader *reader, uint64_t abbrev_number) /* skip 255th record */ uleb128(&p); /* tag */ p++; /* has_children */ - /* skip content */ - for (;;) { - uint64_t at = uleb128(&p); - uint64_t form = uleb128(&p); - if (!at && !form) break; - } - for (n = uleb128(&p); abbrev_number != n; n = uleb128(&p)) { + di_skip_die_attributes(&p); + for (uint64_t n = uleb128(&p); abbrev_number != n; n = uleb128(&p)) { if (n == 0) { fprintf(stderr,"%d: Abbrev Number %"PRId64" not found\n",__LINE__, abbrev_number); exit(1); } uleb128(&p); /* tag */ p++; /* has_children */ - /* skip content */ - for (;;) { - uint64_t at = uleb128(&p); - uint64_t form = uleb128(&p); - if (!at && !form) break; - } + di_skip_die_attributes(&p); } return p; } @@ -1234,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]); @@ -1255,16 +1393,16 @@ div_inspect(DebugInfoValue *v) { switch (v->type) { case VAL_uint: - fprintf(stderr,"%d: type:%d size:%zx v:%lx\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:%ld\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; } @@ -1315,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; @@ -1325,31 +1533,53 @@ 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; } } +static uint64_t +read_dw_form_addr(DebugInfoReader *reader, const char **ptr) +{ + const char *p = *ptr; + *ptr = p + reader->address_size; + if (reader->address_size == 4) { + return read_uint32(&p); + } else if (reader->address_size == 8) { + return read_uint64(&p); + } else { + fprintf(stderr,"unknown address_size:%d", reader->address_size); + abort(); + } +} + 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) { @@ -1361,8 +1591,66 @@ ranges_include(DebugInfoReader *reader, ranges_t *ptr, uint64_t addr) } else if (ptr->ranges_set) { /* TODO: support base address selection entry */ - char *p = reader->obj->debug_ranges.ptr + ptr->ranges; + const char *p; uint64_t base = ptr->low_pc_set ? ptr->low_pc : reader->current_low_pc; + bool base_valid = true; + 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; + if (rle == DW_RLE_end_of_list) break; + switch (rle) { + case DW_RLE_base_addressx: + uleb128(&p); + base_valid = false; /* not supported yet */ + break; + case DW_RLE_startx_endx: + uleb128(&p); + uleb128(&p); + break; + case DW_RLE_startx_length: + uleb128(&p); + uleb128(&p); + break; + case DW_RLE_offset_pair: + if (!base_valid) break; + from = (uintptr_t)base + uleb128(&p); + to = (uintptr_t)base + uleb128(&p); + break; + case DW_RLE_base_address: + base = read_dw_form_addr(reader, &p); + base_valid = true; + break; + case DW_RLE_start_end: + from = (uintptr_t)read_dw_form_addr(reader, &p); + to = (uintptr_t)read_dw_form_addr(reader, &p); + break; + case DW_RLE_start_length: + from = (uintptr_t)read_dw_form_addr(reader, &p); + to = from + uleb128(&p); + break; + } + if (from <= addr && addr < to) { + return from; + } + } + return false; + } + p = reader->obj->debug_ranges.ptr + ptr->ranges; for (;;) { uintptr_t from = read_uintptr(&p); uintptr_t to = read_uintptr(&p); @@ -1372,7 +1660,7 @@ ranges_include(DebugInfoReader *reader, ranges_t *ptr, uint64_t addr) base = to; } else if (base + from <= addr && addr < base + to) { - return from; + return (uintptr_t)base + from; } } } @@ -1430,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; } @@ -1462,30 +1751,76 @@ 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; } static void -read_abstract_origin(DebugInfoReader *reader, uint64_t abstract_origin, line_info_t *line) +read_abstract_origin(DebugInfoReader *reader, uint64_t form, uint64_t abstract_origin, line_info_t *line) { - char *p = reader->p; - char *q = reader->q; + const char *p = reader->p; + const char *q = reader->q; int level = reader->level; DIE die; - reader->p = reader->current_cu + abstract_origin; + switch (form) { + case DW_FORM_ref1: + case DW_FORM_ref2: + case DW_FORM_ref4: + case DW_FORM_ref8: + case DW_FORM_ref_udata: + reader->p = reader->current_cu + abstract_origin; + break; + case DW_FORM_ref_addr: + goto finish; /* not supported yet */ + case DW_FORM_ref_sig8: + goto finish; /* not supported yet */ + case DW_FORM_ref_sup4: + case DW_FORM_ref_sup8: + goto finish; /* not supported yet */ + default: + goto finish; + } if (!di_read_die(reader, &die)) goto finish; /* enumerate abbrev */ @@ -1507,9 +1842,14 @@ read_abstract_origin(DebugInfoReader *reader, uint64_t abstract_origin, line_inf static void debug_info_read(DebugInfoReader *reader, int num_traces, void **traces, - line_info_t *lines, int offset) -{ - int i; + 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 = {}; @@ -1536,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; @@ -1544,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; @@ -1552,18 +1892,18 @@ debug_info_read(DebugInfoReader *reader, int num_traces, void **traces, /* 1 or 3 */ break; /* goto skip_die; */ case DW_AT_abstract_origin: - read_abstract_origin(reader, v.as.uint64, &line); + read_abstract_origin(reader, v.form, v.as.uint64, &line); break; /* goto skip_die; */ } } /* ranges_inspect(reader, &ranges); */ /* fprintf(stderr,"%d:%tx: %x ",__LINE__,diepos,die.tag); */ - for (i=offset; i < num_traces; i++) { + 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)); @@ -1582,10 +1922,59 @@ 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) { + *ptr = NULL; #ifdef SUPPORT_COMPRESSED_DEBUG_LINE ElfW(Chdr) *chdr = (ElfW(Chdr) *)(file + shdr->sh_offset); unsigned long destsize = chdr->ch_size; @@ -1606,6 +1995,7 @@ uncompress_debug_section(ElfW(Shdr) *shdr, char *file, char **ptr) fail: free(*ptr); + *ptr = NULL; #endif return 0; } @@ -1620,6 +2010,7 @@ fill_lines(int num_traces, void **traces, int check_debuglink, ElfW(Ehdr) *ehdr; ElfW(Shdr) *shdr, *shstr_shdr; ElfW(Shdr) *gnu_debuglink_shdr = NULL; + ElfW(Shdr) *note_gnu_build_id = NULL; int fd; off_t filesize; char *file; @@ -1692,6 +2083,11 @@ fill_lines(int num_traces, void **traces, int check_debuglink, /* if (!strcmp(section_name, ".dynsym")) */ dynsym_shdr = shdr + i; break; + case SHT_NOTE: + if (!strcmp(section_name, ".note.gnu.build-id")) { + note_gnu_build_id = shdr + i; + } + break; case SHT_PROGBITS: if (!strcmp(section_name, ".gnu_debuglink")) { gnu_debuglink_shdr = shdr + i; @@ -1702,7 +2098,11 @@ fill_lines(int num_traces, void **traces, int check_debuglink, ".debug_info", ".debug_line", ".debug_ranges", - ".debug_str" + ".debug_str_offsets", + ".debug_addr", + ".debug_rnglists", + ".debug_str", + ".debug_line_str" }; for (j=0; j < DWARF_SECTION_COUNT; j++) { @@ -1807,6 +2207,13 @@ use_symtab: num_traces, traces, objp, lines, offset); } + if (note_gnu_build_id && check_debuglink) { + ElfW(Nhdr) *nhdr = (ElfW(Nhdr)*) (file + note_gnu_build_id->sh_offset); + const char *build_id = (char *)(nhdr + 1) + nhdr->n_namesz; + follow_debuglink_build_id(build_id, nhdr->n_descsz, + num_traces, traces, + objp, lines, offset); + } goto finish; } @@ -1900,6 +2307,7 @@ fill_lines(int num_traces, void **traces, int check_debuglink, p = file; } else if (header->magic == FAT_CIGAM) { + struct LP(mach_header) *mhp = _NSGetMachExecuteHeader(); struct fat_header *fat = (struct fat_header *)file; char *q = file + sizeof(*fat); uint32_t nfat_arch = __builtin_bswap32(fat->nfat_arch); @@ -1909,9 +2317,9 @@ fill_lines(int num_traces, void **traces, int check_debuglink, cpu_type_t cputype = __builtin_bswap32(arch->cputype); cpu_subtype_t cpusubtype = __builtin_bswap32(arch->cpusubtype); uint32_t offset = __builtin_bswap32(arch->offset); - /* fprintf(stderr,"%d: fat %d %x/%x %x/%x\n",__LINE__, i, _mh_execute_header.cputype,_mh_execute_header.cpusubtype, cputype,cpusubtype); */ - if (_mh_execute_header.cputype == cputype && - (_mh_execute_header.cpusubtype & ~CPU_SUBTYPE_MASK) == cpusubtype) { + /* fprintf(stderr,"%d: fat %d %x/%x %x/%x\n",__LINE__, i, mhp->cputype,mhp->cpusubtype, cputype,cpusubtype); */ + if (mhp->cputype == cputype && + (cpu_subtype_t)(mhp->cpusubtype & ~CPU_SUBTYPE_MASK) == cpusubtype) { p = file + offset; file = p; header = (struct LP(mach_header) *)p; @@ -1950,7 +2358,11 @@ found_mach_header: "__debug_info", "__debug_line", "__debug_ranges", - "__debug_str" + "__debug_str_offsets", + "__debug_addr", + "__debug_rnglists", + "__debug_str", + "__debug_line_str", }; struct LP(segment_command) *scmd = (struct LP(segment_command) *)lcmd; if (strcmp(scmd->segname, "__TEXT") == 0) { @@ -1984,10 +2396,10 @@ found_mach_header: { struct symtab_command *cmd = (struct symtab_command *)lcmd; struct LP(nlist) *nl = (struct LP(nlist) *)(file + cmd->symoff); - char *strtab = file + cmd->stroff, *sname; + char *strtab = file + cmd->stroff, *sname = 0; uint32_t j; - uintptr_t saddr; - /* kprintf("[%2d]: %x/symtab %p\n", i, cmd->cmd, p); */ + uintptr_t saddr = 0; + /* kprintf("[%2d]: %x/symtab %p\n", i, cmd->cmd, (void *)p); */ for (j = 0; j < cmd->nsyms; j++) { uintptr_t symsize, d; struct LP(nlist) *e = &nl[j]; @@ -2039,7 +2451,7 @@ fail: #endif #define HAVE_MAIN_EXE_PATH -#if defined(__FreeBSD__) +#if defined(__FreeBSD__) || defined(__DragonFly__) # include <sys/sysctl.h> #endif /* ssize_t main_exe_path(void) @@ -2048,16 +2460,21 @@ fail: * and returns strlen(binary_filename). * it is NUL terminated. */ -#if defined(__linux__) +#if defined(__linux__) || defined(__NetBSD__) static ssize_t main_exe_path(void) { -# define PROC_SELF_EXE "/proc/self/exe" +# if defined(__linux__) +# define PROC_SELF_EXE "/proc/self/exe" +# elif defined(__NetBSD__) +# define PROC_SELF_EXE "/proc/curproc/exe" +# endif ssize_t len = readlink(PROC_SELF_EXE, binary_filename, PATH_MAX); + if (len < 0) return 0; binary_filename[len] = 0; return len; } -#elif defined(__FreeBSD__) +#elif defined(__FreeBSD__) || defined(__DragonFly__) static ssize_t main_exe_path(void) { @@ -2071,6 +2488,15 @@ main_exe_path(void) len--; /* sysctl sets strlen+1 */ return len; } +#elif defined(HAVE_LIBPROC_H) +static ssize_t +main_exe_path(void) +{ + int len = proc_pidpath(getpid(), binary_filename, PATH_MAX); + if (len == 0) return 0; + binary_filename[len] = 0; + return len; +} #else #undef HAVE_MAIN_EXE_PATH #endif @@ -2092,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); @@ -2131,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; @@ -2161,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; } } @@ -2172,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; diff --git a/addr2line.h b/addr2line.h index 4f6cf179ef..f09b665800 100644 --- a/addr2line.h +++ b/addr2line.h @@ -1,3 +1,5 @@ +#ifndef RUBY_ADDR2LINE_H +#define RUBY_ADDR2LINE_H /********************************************************************** addr2line.h - @@ -8,9 +10,6 @@ **********************************************************************/ -#ifndef RUBY_ADDR2LINE_H -#define RUBY_ADDR2LINE_H - #if (defined(USE_ELF) || defined(HAVE_MACH_O_LOADER_H)) void diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 3bf1568cd9..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,145 +0,0 @@ ---- -version: '{build}' -shallow_clone: true -platform: - - x64 -environment: - ruby_version: "24-%Platform%" - zlib_version: "1.2.11" - matrix: - # to reduce time for finishing all jobs, run the slowest msys2 build first. - - build: msys2 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - - build: vs - vs: 120 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - - build: vs - vs: 140 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - GEMS_FOR_TEST: "timezone tzinfo" -notifications: - - provider: Webhook - url: - secure: iMINHMS0nZabaDsxN9omRDsekxzVvAAzEq5ev7lN6vZ6r9zNhl3/F/7POIVyahAwVFpRDeQT/iUugpAGWmOt3eGL/lZIdqiJFZ9DjPSkP68= # #alerts - method: POST - # "icon_url" is the url used by `provider: Slack` - body: >- - {{^isPullRequest}} - { - "attachments": [ - { - "title": "Build {{projectName}} {{buildVersion}} {{status}}", - "fallback": "AppVeyor Build {{projectName}} {{buildVersion}} {{status}}", - "title_link": "{{buildUrl}}", - "text": "Commit <{{commitUrl}}|{{commitId}}> by {{commitAuthor}} on {{commitDate}}: _{{commitMessage}}_", - {{#passed}} - "color": "#44ee44" - {{/passed}} - {{#failed}} - "color": "#ee4444" - {{/failed}} - } - ], - "icon_url": "https://slack-files2.s3-us-west-2.amazonaws.com/bot_icons/2018-02-10/314363543719_48.png", - "username": "AppVeyor CI" - } - {{/isPullRequest}} - on_build_success: false - on_build_failure: true - on_build_status_changed: true -for: -- - matrix: - only: - - build: vs - install: - - ver - - chcp - - SET BITS=%Platform:x86=32% - - SET BITS=%BITS:x=% - - SET OPENSSL_DIR=c:\OpenSSL-Win%BITS% - - CALL SET vcvars=%%^VS%VS%COMNTOOLS^%%..\..\VC\vcvarsall.bat - - SET vcvars - - '"%vcvars%" %Platform:x64=amd64%' - - SET ruby_path=C:\Ruby%ruby_version:-x86=% - - SET PATH=\usr\local\bin;%ruby_path%\bin;%PATH%;C:\msys64\mingw64\bin;C:\msys64\usr\bin - - ruby --version - - 'cl' - - echo> Makefile srcdir=. - - echo>> Makefile MSC_VER=0 - - echo>> Makefile RT=none - - echo>> Makefile RT_VER=0 - - echo>> Makefile BUILTIN_ENCOBJS=nul - - type win32\Makefile.sub >> Makefile - - nmake %mflags% touch-unicode-files - - nmake %mflags% up incs UNICODE_FILES=. - - del Makefile - - mkdir \usr\local\bin - - mkdir \usr\local\include - - mkdir \usr\local\lib - - appveyor DownloadFile https://zlib.net/zlib%zlib_version:.=%.zip - - 7z x -o%APPVEYOR_BUILD_FOLDER%\ext\zlib zlib%zlib_version:.=%.zip - - for %%I in (%OPENSSL_DIR%\*.dll) do mklink /h \usr\local\bin\%%~nxI %%I - - mkdir %Platform%-mswin_%vs% - - ps: Get-ChildItem "win32" -Recurse | foreach {$_.Attributes = 'Readonly'} - - ps: Get-Item $env:Platform"-mswin_"$env:vs | foreach {$_.Attributes = 'Normal'} - build_script: - - 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:\=/% - - nmake -l - - nmake install-nodoc - - \usr\bin\ruby -v -e "p :locale => Encoding.find('locale'), :filesystem => Encoding.find('filesystem')" - - if not "%GEMS_FOR_TEST%" == "" \usr\bin\gem install --no-document %GEMS_FOR_TEST% - test_script: - - set /a JOBS=%NUMBER_OF_PROCESSORS% - - nmake -l "TESTOPTS=-v -q" btest - - nmake -l "TESTOPTS=-v -q" test-basic - - nmake -l "TESTOPTS=-q --subprocess-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 --subprocess-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 test-spec MSPECOPT=-fs # not using `-j` because sometimes `mspec -j` silently dies on Windows -- - matrix: - only: - - build: msys2 - install: - - ver - - chcp - - set /a JOBS=%NUMBER_OF_PROCESSORS% - - set MSYS_NO_PATHCONV=1 - - SET MSYSTEM=%Platform:x86=32% - - SET MSYSTEM=%MSYSTEM:x=MINGW% - - SET MSYS2_ARCH=%Platform:x86=i686% - - SET MSYS2_ARCH=%MSYS2_ARCH:x64=x86_64% - - set MSYSTEM_PREFIX=/mingw64 - - set MINGW_CHOST=%MSYS2_ARCH%-w64-mingw32 - - SET ruby_path=C:\Ruby%ruby_version:-x86=% - - cd .. - - mkdir build - - mkdir install - - SET PATH=%ruby_path%\bin;C:\msys64\%MSYSTEM%\bin;C:\msys64\usr\bin;%PATH% - - ruby --version - build_script: - # always update database - - pacman -Sy - - pacman -S --noconfirm --needed --noprogressbar mingw-w64-x86_64-toolchain - - pacman -S --noconfirm --needed --noprogressbar mingw-w64-x86_64-gdbm mingw-w64-x86_64-gmp mingw-w64-x86_64-libffi mingw-w64-x86_64-ncurses mingw-w64-x86_64-readline mingw-w64-x86_64-zlib - - cd %APPVEYOR_BUILD_FOLDER% - - set CFLAGS=-march=%MSYS2_ARCH:_=-% -mtune=generic -O3 -pipe - - set CXXFLAGS=%CFLAGS% - - set CPPFLAGS=-D_FORTIFY_SOURCE=2 -D__USE_MINGW_ANSI_STDIO=1 -DFD_SETSIZE=2048 - - set LDFLAGS=-pipe - - sh -c "autoreconf -fi" - - cd ..\build - - sh ../ruby/configure --disable-install-doc --prefix=/. --build=%MINGW_CHOST% --host=%MINGW_CHOST% --target=%MINGW_CHOST% - - mingw32-make -j%JOBS% up - - mingw32-make -j%JOBS% - - mingw32-make DESTDIR=../install install-nodoc - - if not "%GEMS_FOR_TEST%" == "" ..\install\bin\gem install --no-document %GEMS_FOR_TEST% - test_script: - - mingw32-make test - - mingw32-make test-all TESTOPTS="--retry --job-status=normal --show-skip --subprocess-timeout-scale=1.5 --excludes=../ruby/test/excludes/_appveyor -j %JOBS% --exclude win32ole --exclude test_open-uri" - # separately execute tests without -j which may crash worker with -j. - - mingw32-make test-all TESTOPTS="--retry --job-status=normal --show-skip --subprocess-timeout-scale=1.5 --excludes=../ruby/test/excludes/_appveyor" TESTS="../ruby/test/win32ole ../ruby/test/open-uri/test_open-uri.rb" - - mingw32-make test-spec MSPECOPT=-fs # not using `-j` because sometimes `mspec -j` silently dies on Windows @@ -11,23 +11,65 @@ **********************************************************************/ +#include "debug_counter.h" +#include "id.h" +#include "internal.h" +#include "internal/array.h" +#include "internal/compar.h" +#include "internal/enum.h" +#include "internal/gc.h" +#include "internal/hash.h" +#include "internal/numeric.h" +#include "internal/object.h" +#include "internal/proc.h" +#include "internal/rational.h" +#include "internal/vm.h" +#include "probes.h" #include "ruby/encoding.h" -#include "ruby/util.h" #include "ruby/st.h" -#include "probes.h" -#include "id.h" -#include "debug_counter.h" -#include "gc.h" +#include "ruby/util.h" #include "transient_heap.h" -#include "internal.h" +#include "builtin.h" #if !ARRAY_DEBUG +# undef NDEBUG # define NDEBUG #endif #include "ruby_assert.h" 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 @@ -35,37 +77,41 @@ VALUE rb_cArray; #define ARY_MAX_SIZE (LONG_MAX / (int)sizeof(VALUE)) #define SMALL_ARRAY_LEN 16 -# define ARY_SHARED_P(ary) \ - (assert(!FL_TEST((ary), ELTS_SHARED) || !FL_TEST((ary), RARRAY_EMBED_FLAG)), \ - FL_TEST((ary),ELTS_SHARED)!=0) -# define ARY_EMBED_P(ary) \ - (assert(!FL_TEST((ary), ELTS_SHARED) || !FL_TEST((ary), RARRAY_EMBED_FLAG)), \ - FL_TEST((ary), RARRAY_EMBED_FLAG)!=0) +RBIMPL_ATTR_MAYBE_UNUSED() +static int +should_be_T_ARRAY(VALUE ary) +{ + return RB_TYPE_P(ary, T_ARRAY); +} #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)), RARRAY(a)->as.heap.aux.capa) +#define ARY_HEAP_CAPA(a) (assert(!ARY_EMBED_P(a)), assert(!ARY_SHARED_ROOT_P(a)), \ + RARRAY(a)->as.heap.aux.capa) #define ARY_EMBED_PTR(a) (assert(ARY_EMBED_P(a)), RARRAY(a)->as.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))) -#define ARY_HEAP_SIZE(a) (assert(!ARY_EMBED_P(a)), assert(ARY_OWNS_HEAP_P(a)), ARY_HEAP_CAPA(a) * sizeof(VALUE)) + (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), RARRAY_SHARED_FLAG|RARRAY_EMBED_FLAG)) -#define ARY_OWNS_HEAP_P(a) (!FL_TEST((a), ELTS_SHARED|RARRAY_EMBED_FLAG)) #define FL_SET_EMBED(a) do { \ assert(!ARY_SHARED_P(a)); \ FL_SET((a), RARRAY_EMBED_FLAG); \ RARY_TRANSIENT_UNSET(a); \ ary_verify(a); \ } while (0) + #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)); \ @@ -75,7 +121,6 @@ VALUE rb_cArray; #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) @@ -107,7 +152,7 @@ VALUE rb_cArray; } \ } 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)); \ @@ -116,31 +161,98 @@ VALUE rb_cArray; RARRAY(ary)->as.heap.aux.capa = (n); \ } while (0) -#define ARY_SHARED(ary) (assert(ARY_SHARED_P(ary)), RARRAY(ary)->as.heap.aux.shared) #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_)); \ - RB_OBJ_WRITE(_ary_, &RARRAY(_ary_)->as.heap.aux.shared, _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) (FL_TEST((ary), RARRAY_SHARED_ROOT_FLAG)) -#define ARY_SHARED_NUM(ary) \ - (assert(ARY_SHARED_ROOT_P(ary)), RARRAY(ary)->as.heap.aux.capa) -#define ARY_SHARED_OCCUPIED(ary) (ARY_SHARED_NUM(ary) == 1) -#define ARY_SET_SHARED_NUM(ary, value) do { \ + +#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); \ } while (0) -#define ARY_SET(a, i, v) RARRAY_ASET((assert(!ARY_SHARED_P(a)), (a)), (i), (v)) +static inline void +ARY_SET(VALUE a, long i, VALUE v) +{ + assert(!ARY_SHARED_P(a)); + assert(!OBJ_FROZEN(a)); + + RARRAY_ASET(a, i, 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 @@ -151,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; + 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 @@ -178,9 +290,11 @@ ary_verify_(VALUE ary, const char *file, int line) #endif } +#if USE_TRANSIENT_HEAP if (RARRAY_TRANSIENT_P(ary)) { assert(rb_transient_heap_managed_ptr_p(RARRAY_CONST_PTR_TRANSIENT(ary))); } +#endif rb_transient_heap_verify(); @@ -188,7 +302,8 @@ ary_verify_(VALUE ary, const char *file, int line) } void -rb_ary_verify(VALUE ary){ +rb_ary_verify(VALUE ary) +{ ary_verify(ary); } #else @@ -213,10 +328,10 @@ rb_ary_ptr_use_end(VALUE ary) } void -rb_mem_clear(register VALUE *mem, register long size) +rb_mem_clear(VALUE *mem, long size) { while (size--) { - *mem++ = Qnil; + *mem++ = Qnil; } } @@ -224,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); }); } @@ -232,7 +347,7 @@ static inline void memfill(register VALUE *mem, register long size, register VALUE val) { while (size--) { - *mem++ = val; + *mem++ = val; } } @@ -240,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); }); } @@ -310,14 +425,16 @@ ary_heap_free(VALUE ary) } } -static void +static size_t ary_heap_realloc(VALUE ary, size_t new_capa) { + size_t alloc_capa = new_capa; size_t old_capa = ARY_HEAP_CAPA(ary); if (RARRAY_TRANSIENT_P(ary)) { if (new_capa <= old_capa) { /* do nothing */ + alloc_capa = old_capa; } else { VALUE *new_ptr = rb_transient_heap_alloc(ary, sizeof(VALUE) * new_capa); @@ -335,6 +452,8 @@ ary_heap_realloc(VALUE ary, size_t new_capa) SIZED_REALLOC_N(RARRAY(ary)->as.heap.ptr, VALUE, new_capa, old_capa); } ary_verify(ary); + + return alloc_capa; } #if USE_TRANSIENT_HEAP @@ -342,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)); @@ -391,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) { @@ -398,7 +535,8 @@ 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); VALUE *ptr = ary_heap_alloc(ary, capacity); @@ -409,9 +547,9 @@ ary_resize_capa(VALUE ary, long capacity) ARY_SET_HEAP_LEN(ary, len); } else { - ary_heap_realloc(ary, capacity); + new_capa = ary_heap_realloc(ary, capacity); } - ARY_SET_CAPA(ary, capacity); + ARY_SET_CAPA(ary, new_capa); } else { if (!ARY_EMBED_P(ary)) { @@ -449,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); @@ -461,52 +599,54 @@ ary_double_capa(VALUE ary, long min) } static void -rb_ary_decrement_share(VALUE shared) +rb_ary_decrement_share(VALUE shared_root) { - if (shared) { - long num = ARY_SHARED_NUM(shared) - 1; - if (num == 0) { - rb_ary_free(shared); - rb_gc_force_recycle(shared); - } - else if (num > 0) { - ARY_SET_SHARED_NUM(shared, 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 = RARRAY(ary)->as.heap.aux.shared; - rb_ary_decrement_share(shared); + 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) +rb_ary_increment_share(VALUE shared_root) { - long num = ARY_SHARED_NUM(shared); - if (num >= 0) { - ARY_SET_SHARED_NUM(shared, num + 1); + 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; + return shared_root; } static void -rb_ary_set_shared(VALUE ary, VALUE shared) +rb_ary_set_shared(VALUE ary, VALUE shared_root) { - rb_ary_increment_share(shared); + rb_ary_increment_share(shared_root); FL_SET_SHARED(ary); - ARY_SET_SHARED(ary, shared); + RB_DEBUG_COUNTER_INC(obj_ary_shared_create); + ARY_SET_SHARED(ary, shared_root); } static inline void @@ -517,34 +657,33 @@ rb_ary_modify_check(VALUE ary) } void -rb_ary_modify(VALUE ary) +rb_ary_cancel_sharing(VALUE ary) { - rb_ary_modify_check(ary); if (ARY_SHARED_P(ary)) { - long shared_len, len = RARRAY_LEN(ary); - VALUE shared = ARY_SHARED(ary); + long shared_len, len = RARRAY_LEN(ary); + VALUE shared_root = ARY_SHARED_ROOT(ary); - ary_verify(shared); + ary_verify(shared_root); - if (len <= RARRAY_EMBED_LEN_MAX) { - const VALUE *ptr = ARY_HEAP_PTR(ary); + if (len <= ary_embed_capa(ary)) { + const VALUE *ptr = ARY_HEAP_PTR(ary); FL_UNSET_SHARED(ary); FL_SET_EMBED(ary); - MEMCPY((VALUE *)ARY_EMBED_PTR(ary), ptr, VALUE, len); - rb_ary_decrement_share(shared); + MEMCPY((VALUE *)ARY_EMBED_PTR(ary), ptr, VALUE, len); + rb_ary_decrement_share(shared_root); ARY_SET_EMBED_LEN(ary, len); } - else if (ARY_SHARED_OCCUPIED(shared) && len > ((shared_len = RARRAY_LEN(shared))>>1)) { - long shift = RARRAY_CONST_PTR_TRANSIENT(ary) - RARRAY_CONST_PTR_TRANSIENT(shared); - FL_UNSET_SHARED(ary); - ARY_SET_PTR(ary, RARRAY_CONST_PTR_TRANSIENT(shared)); - ARY_SET_CAPA(ary, shared_len); + else if (ARY_SHARED_ROOT_OCCUPIED(shared_root) && len > ((shared_len = RARRAY_LEN(shared_root))>>1)) { + long shift = RARRAY_CONST_PTR_TRANSIENT(ary) - RARRAY_CONST_PTR_TRANSIENT(shared_root); + FL_UNSET_SHARED(ary); + ARY_SET_PTR(ary, RARRAY_CONST_PTR_TRANSIENT(shared_root)); + ARY_SET_CAPA(ary, shared_len); RARRAY_PTR_USE_TRANSIENT(ary, ptr, { - MEMMOVE(ptr, ptr+shift, VALUE, len); - }); - FL_SET_EMBED(shared); - rb_ary_decrement_share(shared); - } + MEMMOVE(ptr, ptr+shift, VALUE, len); + }); + FL_SET_EMBED(shared_root); + rb_ary_decrement_share(shared_root); + } else { VALUE *ptr = ary_heap_alloc(ary, len); MEMCPY(ptr, ARY_HEAP_PTR(ary), VALUE, len); @@ -553,11 +692,18 @@ rb_ary_modify(VALUE ary) ARY_SET_PTR(ary, ptr); } - rb_gc_writebarrier_remember(ary); + rb_gc_writebarrier_remember(ary); } ary_verify(ary); } +void +rb_ary_modify(VALUE ary) +{ + rb_ary_modify_check(ary); + rb_ary_cancel_sharing(ary); +} + static VALUE ary_ensure_room_for_push(VALUE ary, long add_len) { @@ -566,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) { - VALUE shared = ARY_SHARED(ary); - if (ARY_SHARED_OCCUPIED(shared)) { - if (ARY_HEAP_PTR(ary) - RARRAY_CONST_PTR_TRANSIENT(shared) + new_len <= RARRAY_LEN(shared)) { - rb_ary_modify_check(ary); + 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); ary_verify(ary); - ary_verify(shared); - return shared; - } - 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(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); + } 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); @@ -608,12 +754,16 @@ ary_ensure_room_for_push(VALUE ary, long add_len) /* * call-seq: - * ary.freeze -> ary + * array.freeze -> self + * + * Freezes +self+; returns +self+: * - * Calls Object#freeze on +ary+ to prevent any further - * modification. A RuntimeError will be raised if a modification - * attempt is made. + * a = [] + * a.frozen? # => false + * a.freeze + * a.frozen? # => true * + * An attempt to modify a frozen \Array raises FrozenError. */ VALUE @@ -633,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 == RARRAY(ary2)->as.heap.aux.shared && - 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); @@ -653,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 @@ -665,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); } @@ -694,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 @@ -708,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); @@ -723,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; @@ -736,8 +906,79 @@ rb_ary_new_from_values(long n, const VALUE *elts) return rb_ary_tmp_new_from_values(rb_cArray, n, elts); } +static VALUE +ec_ary_alloc_embed(rb_execution_context_t *ec, VALUE klass, long capa) +{ + 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); + */ + return (VALUE)ary; +} + +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)"); + } + if (capa > ARY_MAX_SIZE) { + rb_raise(rb_eArgError, "array size too big"); + } + + RUBY_DTRACE_CREATE_HOOK(ARRAY, capa); + + 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)); + + ptr = ary_heap_alloc(ary, capa); + ARY_SET_PTR(ary, ptr); + ARY_SET_HEAP_LEN(ary, 0); + } + + return ary; +} + VALUE -rb_ary_tmp_new(long capa) +rb_ec_ary_new_from_values(rb_execution_context_t *ec, long n, const VALUE *elts) +{ + VALUE ary; + + ary = ec_ary_new(ec, rb_cArray, n); + if (n > 0 && elts) { + ary_memcpy(ary, 0, n, elts); + ARY_SET_LEN(ary, n); + } + + return ary; +} + +VALUE +rb_ary_hidden_new(long capa) { VALUE ary = ary_new(0, capa); rb_ary_transient_heap_evacuate(ary, TRUE); @@ -745,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; } @@ -758,6 +998,12 @@ void rb_ary_free(VALUE ary) { if (ARY_OWNS_HEAP_P(ary)) { + if (USE_DEBUG_COUNTER && + !ARY_SHARED_ROOT_P(ary) && + ARY_HEAP_CAPA(ary) > RARRAY_LEN(ary)) { + RB_DEBUG_COUNTER_INC(obj_ary_extracapa); + } + if (RARRAY_TRANSIENT_P(ary)) { RB_DEBUG_COUNTER_INC(obj_ary_transient); } @@ -767,7 +1013,14 @@ rb_ary_free(VALUE ary) } } else { - RB_DEBUG_COUNTER_INC(obj_ary_embed); + RB_DEBUG_COUNTER_INC(obj_ary_embed); + } + + if (ARY_SHARED_P(ary)) { + RB_DEBUG_COUNTER_INC(obj_ary_shared); + } + if (ARY_SHARED_ROOT_P(ary) && ARY_SHARED_ROOT_OCCUPIED(ary)) { + RB_DEBUG_COUNTER_INC(obj_ary_shared_root_occupied); } } @@ -775,61 +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(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_NUM(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)); - rb_ary_transient_heap_evacuate(ary, TRUE); - ptr = ARY_HEAP_PTR(ary); - - FL_UNSET_EMBED(shared); - ARY_SET_LEN((VALUE)shared, capa); - ARY_SET_PTR((VALUE)shared, ptr); - ary_mem_clear((VALUE)shared, len, capa - len); - FL_SET_SHARED_ROOT(shared); - ARY_SET_SHARED_NUM((VALUE)shared, 1); - FL_SET_SHARED(ary); - ARY_SET_SHARED(ary, (VALUE)shared); - OBJ_FREEZE(shared); - - ary_verify((VALUE)shared); + + 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, shared); + + ary_verify(shared); ary_verify(ary); - return (VALUE)shared; + + return shared; } } @@ -838,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; @@ -874,23 +1137,24 @@ rb_check_to_array(VALUE ary) return rb_check_convert_type_with_id(ary, T_ARRAY, "Array", idTo_a); } +VALUE +rb_to_array(VALUE ary) +{ + return rb_convert_type_with_id(ary, T_ARRAY, "Array", idTo_a); +} + /* * call-seq: - * Array.try_convert(obj) -> array or nil + * Array.try_convert(object) -> object, new_array, or nil * - * Tries to convert +obj+ into an array, using +to_ary+ method. Returns the - * converted array or +nil+ if +obj+ cannot be converted for any reason. - * This method can be used to check if an argument is an array. + * If +object+ is an \Array object, returns +object+. * - * Array.try_convert([1]) #=> [1] - * Array.try_convert("1") #=> nil + * Otherwise if +object+ responds to <tt>:to_ary</tt>, + * calls <tt>object.to_ary</tt> and returns the result. * - * if tmp = Array.try_convert(arg) - * # the argument is an array - * elsif tmp = String.try_convert(arg) - * # the argument is a string - * end + * Returns +nil+ if +object+ does not respond to <tt>:to_ary</tt> * + * Raises an exception unless <tt>object.to_ary</tt> returns an \Array object. */ static VALUE @@ -899,60 +1163,76 @@ 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(size=0, default=nil) - * Array.new(array) - * Array.new(size) {|index| block } - * - * Returns a new array. - * - * In the first form, if no arguments are sent, the new array will be empty. - * When a +size+ and an optional +default+ are sent, an array is created with - * +size+ copies of +default+. Take notice that all elements will reference the - * same object +default+. - * - * The second form creates a copy of the array passed as a parameter (the - * array is generated by calling to_ary on the parameter). - * - * first_array = ["Matz", "Guido"] + * Array.new -> new_empty_array + * Array.new(array) -> new_array + * Array.new(size) -> new_array + * Array.new(size, default_value) -> new_array + * Array.new(size) {|index| ... } -> new_array * - * second_array = Array.new(first_array) #=> ["Matz", "Guido"] + * Returns a new \Array. * - * first_array.equal? second_array #=> false + * With no block and no arguments, returns a new empty \Array object. * - * In the last form, an array of the given size is created. Each element in - * this array is created by passing the element's index to the given block - * and storing the return value. + * With no block and a single \Array argument +array+, + * returns a new \Array formed from +array+: * - * Array.new(3) {|index| index ** 2} - * # => [0, 1, 4] + * a = Array.new([:foo, 'bar', 2]) + * a.class # => Array + * a # => [:foo, "bar", 2] * - * == Common gotchas + * With no block and a single \Integer argument +size+, + * returns a new \Array of the given size + * whose elements are all +nil+: * - * When sending the second parameter, the same object will be used as the - * value for all the array elements: + * a = Array.new(3) + * a # => [nil, nil, nil] * - * a = Array.new(2, Hash.new) - * # => [{}, {}] + * With no block and arguments +size+ and +default_value+, + * returns an \Array of the given size; + * each element is that same +default_value+: * - * a[0]['cat'] = 'feline' - * a # => [{"cat"=>"feline"}, {"cat"=>"feline"}] + * a = Array.new(3, 'x') + * a # => ['x', 'x', 'x'] * - * a[1]['cat'] = 'Felix' - * a # => [{"cat"=>"Felix"}, {"cat"=>"Felix"}] + * With a block and argument +size+, + * 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: * - * Since all the Array elements store the same hash, changes to one of them - * will affect them all. + * a = Array.new(3) {|index| "Element #{index}" } + * a # => ["Element 0", "Element 1", "Element 2"] * - * If multiple copies are what you want, you should use the block - * version which uses the result of that block each time an element - * of the array needs to be initialized: - * - * a = Array.new(2) {Hash.new} - * a[0]['cat'] = 'feline' - * a # => [{"cat"=>"feline"}, {}] + * Raises ArgumentError if +size+ is negative. * + * With a block and no argument, + * or a single argument +0+, + * ignores the block and returns a new empty \Array. */ static VALUE @@ -963,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; } @@ -1038,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); } @@ -1069,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); @@ -1094,9 +1374,59 @@ ary_make_partial(VALUE ary, VALUE klass, long offset, long len) } static VALUE +ary_make_partial_step(VALUE ary, VALUE klass, long offset, long len, long step) +{ + assert(offset >= 0); + assert(len >= 0); + assert(offset+len <= RARRAY_LEN(ary)); + assert(step != 0); + + const VALUE *values = RARRAY_CONST_PTR_TRANSIENT(ary); + const long orig_len = 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 = roomof(len, ustep); + + long i; + long j = offset + ((step > 0) ? 0 : (orig_len - 1)); + + VALUE result = ary_new(klass, len); + 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]); + j += step; + } + ARY_SET_EMBED_LEN(result, len); + } + else { + RARRAY_PTR_USE_TRANSIENT(result, ptr, { + for (i = 0; i < len; ++i) { + RB_OBJ_WRITE(result, ptr+i, values[j]); + j += step; + } + }); + ARY_SET_LEN(result, len); + } + + return result; +} + +static VALUE ary_make_shared_copy(VALUE ary) { - return ary_make_partial(ary, rb_obj_class(ary), 0, RARRAY_LEN(ary)); + return ary_make_partial(ary, rb_cArray, 0, RARRAY_LEN(ary)); } enum ary_take_pos_flags @@ -1113,38 +1443,39 @@ ary_take_first_or_last(int argc, const VALUE *argv, VALUE ary, enum ary_take_pos long offset = 0; argc = rb_check_arity(argc, 0, 1); - /* the case optional argument is ommited should be handled in + /* the case optional argument is omitted should be handled in * callers of this function. if another arity case is added, * this arity check needs to rewrite. */ - RUBY_ASSERT_WHEN(TRUE, argc == 1); + RUBY_ASSERT_ALWAYS(argc == 1); 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); } /* * call-seq: - * ary << obj -> ary + * array << object -> self + * + * Appends +object+ to +self+; returns +self+: * - * Append---Pushes the given object on to the end of this array. This - * expression returns the array itself, so several appends - * may be chained together. + * a = [:foo, 'bar', 2] + * a << :baz # => [:foo, "bar", 2, :baz] * - * a = [ 1, 2 ] - * a << "c" << "d" << [ 3, 4 ] - * #=> [ 1, 2, "c", "d", [ 3, 4 ] ] - * a - * #=> [ 1, 2, "c", "d", [ 3, 4 ] ] + * 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]] * */ @@ -1154,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); @@ -1173,19 +1504,24 @@ rb_ary_cat(VALUE ary, const VALUE *argv, long len) /* * call-seq: - * ary.push(obj, ...) -> ary - * ary.append(obj, ...) -> ary - * - * Append --- Pushes the given object(s) on to the end of this array. This - * expression returns the array itself, so several appends - * may be chained together. See also Array#pop for the opposite - * effect. - * - * a = [ "a", "b", "c" ] - * a.push("d", "e", "f") - * #=> ["a", "b", "c", "d", "e", "f"] - * [1, 2, 3].push(4).push(5) - * #=> [1, 2, 3, 4, 5] + * array.push(*objects) -> self + * + * 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. + * + * Related: #pop, #shift, #unshift. */ static VALUE @@ -1202,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); @@ -1215,20 +1551,33 @@ rb_ary_pop(VALUE ary) /* * call-seq: - * ary.pop -> obj or nil - * ary.pop(n) -> new_ary + * array.pop -> object or nil + * array.pop(n) -> new_array + * + * Removes and returns trailing elements. + * + * 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"] * - * Removes the last element from +self+ and returns it, or - * +nil+ if the array is empty. + * Returns +nil+ if the array is empty. * - * If a number +n+ is given, returns an array of the last +n+ elements - * (or less) just like <code>array.slice!(-n, n)</code> does. See also - * Array#push for the opposite effect. + * When a non-negative \Integer argument +n+ is given and is in range, * - * a = [ "a", "b", "c", "d" ] - * a.pop #=> "d" - * a.pop(2) #=> ["b", "c"] - * a #=> ["a"] + * 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] + * + * Related: #push, #shift, #unshift. */ static VALUE @@ -1237,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); @@ -1253,55 +1602,49 @@ 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_OCCUPIED(ARY_SHARED(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; } /* * call-seq: - * ary.shift -> obj or nil - * ary.shift(n) -> new_ary + * array.shift -> object or nil + * array.shift(n) -> new_array + * + * 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] + * + * Returns +nil+ if +self+ is empty. + * + * When positive \Integer argument +n+ is given, removes the first +n+ elements; + * returns those elements in a new \Array: * - * Removes the first element of +self+ and returns it (shifting all - * other elements down by one). Returns +nil+ if the array - * is empty. + * a = [:foo, 'bar', 2] + * a.shift(2) # => [:foo, 'bar'] + * a # => [2] * - * If a number +n+ is given, returns an array of the first +n+ elements - * (or less) just like <code>array.slice!(0, n)</code> does. With +ary+ - * containing only the remainder elements, not including what was shifted to - * +new_ary+. See also Array#unshift for the opposite effect. + * If +n+ is as large as or larger than <tt>self.length</tt>, + * removes all elements; returns those elements in a new \Array: * - * args = [ "-m", "-q", "filename" ] - * args.shift #=> "-m" - * args #=> ["-q", "filename"] + * a = [:foo, 'bar', 2] + * a.shift(3) # => [:foo, 'bar', 2] * - * args = [ "-m", "-q", "filename" ] - * args.shift(2) #=> ["-m", "-q"] - * args #=> ["filename"] + * If +n+ is zero, returns a new empty \Array; +self+ is unmodified. + * + * Related: #push, #pop, #unshift. */ static VALUE @@ -1311,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); @@ -1325,108 +1668,133 @@ rb_ary_shift_m(int argc, VALUE *argv, VALUE ary) MJIT_FUNC_EXPORTED VALUE rb_ary_behead(VALUE ary, long n) { - if(n<=0) return ary; + if (n <= 0) { + return ary; + } rb_ary_modify_check(ary); - if (ARY_SHARED_P(ary)) { - if (ARY_SHARED_OCCUPIED(ARY_SHARED(ary))) { - setup_occupied_shared: - ary_mem_clear(ary, 0, n); - } - ARY_INCREASE_PTR(ary, n); - } - else { - if (RARRAY_LEN(ary) < ARY_DEFAULT_SIZE) { + + 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 */ - } - else { - ary_make_shared(ary); - goto setup_occupied_shared; - } + 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); + } + else if (ARY_SHARED_ROOT_OCCUPIED(ARY_SHARED_ROOT(ary))) { + ary_mem_clear(ary, 0, n); } - ARY_INCREASE_LEN(ary, -n); + ARY_INCREASE_PTR(ary, n); + ARY_INCREASE_LEN(ary, -n); ary_verify(ary); + return ary; } static VALUE -ary_ensure_room_for_unshift(VALUE ary, int argc) +make_room_for_unshift(VALUE ary, const VALUE *head, VALUE *sharedp, int argc, long capa, long len) +{ + if (head - sharedp < argc) { + long room = capa - len - argc; + + room -= room >> 4; + MEMMOVE((VALUE *)sharedp + argc + room, head, VALUE, len); + head = sharedp + argc + room; + } + ARY_SET_PTR(ary, head - argc); + assert(ARY_SHARED_ROOT_OCCUPIED(ARY_SHARED_ROOT(ary))); + + ary_verify(ary); + return ARY_SHARED_ROOT(ary); +} + +static VALUE +ary_modify_for_unshift(VALUE ary, int argc) { long len = RARRAY_LEN(ary); long new_len = len + argc; long capa; const VALUE *head, *sharedp; - if (len > ARY_MAX_SIZE - argc) { - rb_raise(rb_eIndexError, "index %ld too big", new_len); - } - - if (ARY_SHARED_P(ary)) { - VALUE shared = ARY_SHARED(ary); - capa = RARRAY_LEN(shared); - if (ARY_SHARED_OCCUPIED(shared) && capa > new_len) { - head = RARRAY_CONST_PTR_TRANSIENT(ary); - sharedp = RARRAY_CONST_PTR_TRANSIENT(shared); - goto makeroom_if_need; - } - } - 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); - goto makeroom; - makeroom_if_need: - if (head - sharedp < argc) { - long room; - makeroom: - room = capa - new_len; - room -= room >> 4; - MEMMOVE((VALUE *)sharedp + argc + room, head, VALUE, len); - head = sharedp + argc + room; - } - ARY_SET_PTR(ary, head - argc); - assert(ARY_SHARED_OCCUPIED(ARY_SHARED(ary))); - - ary_verify(ary); - return ARY_SHARED(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; + } +} + +static VALUE +ary_ensure_room_for_unshift(VALUE ary, int argc) +{ + long len = RARRAY_LEN(ary); + long new_len = len + argc; + + if (len > ARY_MAX_SIZE - argc) { + rb_raise(rb_eIndexError, "index %ld too big", new_len); + } + else if (! ARY_SHARED_P(ary)) { + return ary_modify_for_unshift(ary, argc); + } + else { + VALUE shared_root = ARY_SHARED_ROOT(ary); + long capa = RARRAY_LEN(shared_root); + + if (! ARY_SHARED_ROOT_OCCUPIED(shared_root)) { + return ary_modify_for_unshift(ary, argc); + } + else if (new_len > capa) { + return ary_modify_for_unshift(ary, argc); + } + else { + const VALUE * head = RARRAY_CONST_PTR_TRANSIENT(ary); + void *sharedp = (void *)RARRAY_CONST_PTR_TRANSIENT(shared_root); + + rb_ary_modify_check(ary); + return make_room_for_unshift(ary, head, sharedp, argc, capa, len); + } } } /* * call-seq: - * ary.unshift(obj, ...) -> ary - * ary.prepend(obj, ...) -> ary + * array.unshift(*objects) -> self + * + * Prepends the given +objects+ to +self+: + * + * a = [:foo, 'bar', 2] + * a.unshift(:bam, :bat) # => [:bam, :bat, :foo, "bar", 2] * - * Prepends objects to the front of +self+, moving other elements upwards. - * See also Array#shift for the opposite effect. + * Array#prepend is an alias for Array#unshift. * - * a = [ "b", "c", "d" ] - * a.unshift("a") #=> ["a", "b", "c", "d"] - * a.unshift(1, 2) #=> [ 1, 2, "a", "b", "c", "d"] + * Related: #push, #pop, #shift. */ static VALUE @@ -1436,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); @@ -1459,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); } @@ -1471,7 +1839,7 @@ rb_ary_entry(VALUE ary, long offset) } VALUE -rb_ary_subseq(VALUE ary, long beg, long len) +rb_ary_subseq_step(VALUE ary, long beg, long len, long step) { VALUE klass; long alen = RARRAY_LEN(ary); @@ -1480,48 +1848,129 @@ rb_ary_subseq(VALUE ary, long beg, long len) if (beg < 0 || len < 0) return Qnil; if (alen < len || alen < beg + len) { - len = alen - beg; + len = alen - beg; } - klass = rb_obj_class(ary); + klass = rb_cArray; if (len == 0) return ary_new(klass, 0); + if (step == 0) + rb_raise(rb_eArgError, "slice step cannot be zero"); + if (step == 1) + return ary_make_partial(ary, klass, beg, len); + else + return ary_make_partial_step(ary, klass, beg, len, step); +} - return ary_make_partial(ary, klass, beg, len); +VALUE +rb_ary_subseq(VALUE ary, long beg, long len) +{ + return rb_ary_subseq_step(ary, beg, len, 1); } +static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e); + /* * call-seq: - * ary[index] -> obj or nil - * ary[start, length] -> new_ary or nil - * ary[range] -> new_ary or nil - * ary.slice(index) -> obj or nil - * ary.slice(start, length) -> new_ary or nil - * ary.slice(range) -> new_ary or nil - * - * Element Reference --- Returns the element at +index+, or returns a - * subarray starting at the +start+ index and continuing for +length+ - * elements, or returns a subarray specified by +range+ of indices. - * - * Negative indices count backward from the end of the array (-1 is the last - * element). For +start+ and +range+ cases the starting index is just before - * an element. Additionally, an empty array is returned when the starting - * index for an element range is at the end of the array. - * - * Returns +nil+ if the index (or starting index) are out of range. - * - * a = [ "a", "b", "c", "d", "e" ] - * a[2] + a[0] + a[1] #=> "cab" - * a[6] #=> nil - * a[1, 2] #=> [ "b", "c" ] - * a[1..3] #=> [ "b", "c", "d" ] - * a[4..7] #=> [ "e" ] - * a[6..10] #=> nil - * a[-3, 3] #=> [ "c", "d", "e" ] - * # special cases - * a[5] #=> nil - * a[6, 1] #=> nil - * a[5, 1] #=> [] - * a[5..10] #=> [] + * array[index] -> object or nil + * array[start, length] -> object or nil + * array[range] -> object or nil + * array[aseq] -> object or nil + * array.slice(index) -> object or nil + * array.slice(start, length) -> object or nil + * array.slice(range) -> object or nil + * array.slice(aseq) -> object or nil + * + * 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" + * + * If +index+ is out of range, returns +nil+. + * + * 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] + * a[2, 2] # => [2] + * + * If <tt>start == self.size</tt> and <tt>length >= 0</tt>, + * returns a new empty \Array. + * + * If +length+ is negative, returns +nil+. + * + * 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] + * + * 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 + * 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) + * a[(7..).step(2)] + * # RangeError (((7..).step(2)) out of range) + * + * 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] + * + * Array#slice is an alias for Array#[]. */ VALUE @@ -1529,18 +1978,18 @@ 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]); } -VALUE +static VALUE 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); } @@ -1548,35 +1997,34 @@ rb_ary_aref2(VALUE ary, VALUE b, VALUE e) MJIT_FUNC_EXPORTED VALUE rb_ary_aref1(VALUE ary, VALUE arg) { - long beg, len; + long beg, len, step; /* 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 */ - switch (rb_range_beg_len(arg, &beg, &len, RARRAY_LEN(ary), 0)) { + /* check if idx is Range or ArithmeticSequence */ + switch (rb_arithmetic_sequence_beg_len_step(arg, &beg, &len, &step, RARRAY_LEN(ary), 0)) { case Qfalse: - break; + break; case Qnil: - return Qnil; + return Qnil; default: - return rb_ary_subseq(ary, beg, len); + return rb_ary_subseq_step(ary, beg, len, step); } + return rb_ary_entry(ary, NUM2LONG(arg)); } /* * call-seq: - * ary.at(index) -> obj or nil + * array.at(index) -> object * - * Returns the element at +index+. A negative index counts from the end of - * +self+. Returns +nil+ if the index is out of range. See also - * Array#[]. + * Returns the element at \Integer offset +index+; does not modify +self+. + * a = [:foo, 'bar', 2] + * a.at(0) # => :foo + * a.at(2) # => 2 * - * a = [ "a", "b", "c", "d", "e" ] - * a.at(0) #=> "a" - * a.at(-1) #=> "e" */ VALUE @@ -1587,81 +2035,131 @@ rb_ary_at(VALUE ary, VALUE pos) /* * call-seq: - * ary.first -> obj or nil - * ary.first(n) -> new_ary + * array.first -> object or nil + * array.first(n) -> new_array + * + * Returns elements from +self+; does not modify +self+. * - * Returns the first element, or the first +n+ elements, of the array. - * If the array is empty, the first form returns +nil+, and the - * second form returns an empty array. See also Array#last for - * the opposite effect. + * When no argument is given, returns the first element: * - * a = [ "q", "r", "s", "t" ] - * a.first #=> "q" - * a.first(2) #=> ["q", "r"] + * a = [:foo, 'bar', 2] + * a.first # => :foo + * a # => [:foo, "bar", 2] + * + * If +self+ is empty, returns +nil+. + * + * 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) # [] + * + * Related: #last. */ - 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); } } /* * call-seq: - * ary.last -> obj or nil - * ary.last(n) -> new_ary + * array.last -> object or nil + * array.last(n) -> new_array + * + * Returns elements from +self+; +self+ is not modified. + * + * When no argument is given, returns the last element: * - * Returns the last element(s) of +self+. If the array is empty, - * the first form returns +nil+. + * a = [:foo, 'bar', 2] + * a.last # => 2 + * a # => [:foo, "bar", 2] * - * See also Array#first for the opposite effect. + * If +self+ is empty, returns +nil+. * - * a = [ "w", "x", "y", "z" ] - * a.last #=> "z" - * a.last(2) #=> ["y", "z"] + * 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) # [] + * + * Related: #first. */ 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); } } /* * call-seq: - * ary.fetch(index) -> obj - * ary.fetch(index, default) -> obj - * ary.fetch(index) {|index| block} -> obj - * - * Tries to return the element at position +index+, but throws an IndexError - * exception if the referenced +index+ lies outside of the array bounds. This - * error can be prevented by supplying a second argument, which will act as a - * +default+ value. - * - * Alternatively, if a block is given it will only be executed when an - * invalid +index+ is referenced. - * - * Negative values of +index+ count from the end of the array. - * - * a = [ 11, 22, 33, 44 ] - * a.fetch(1) #=> 22 - * a.fetch(-1) #=> 44 - * a.fetch(4, 'cat') #=> "cat" - * a.fetch(100) {|i| puts "#{i} is out of bounds"} - * #=> "100 is out of bounds" + * array.fetch(index) -> element + * array.fetch(index, default_value) -> element + * array.fetch(index) {|index| ... } -> element + * + * Returns the element at offset +index+. + * + * 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" + * + * 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" + * + * With argument +index+ and a block, + * returns the element at offset +index+ if index is in range + * (and the block is not called); otherwise calls the block with index and returns its return value: + * + * a = [:foo, 'bar', 2] + * a.fetch(1) {|index| raise 'Cannot happen' } # => "bar" + * a.fetch(50) {|index| "Value for #{index}" } # => "Value for 50" + * */ static VALUE @@ -1674,48 +2172,60 @@ 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); } /* * call-seq: - * ary.find_index(obj) -> int or nil - * ary.find_index {|item| block} -> int or nil - * ary.find_index -> Enumerator - * ary.index(obj) -> int or nil - * ary.index {|item| block} -> int or nil - * ary.index -> Enumerator + * array.index(object) -> integer or nil + * array.index {|element| ... } -> integer or nil + * array.index -> new_enumerator + * + * Returns the index of a specified element. + * + * 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 + * + * Returns +nil+ if no such element found. * - * Returns the _index_ of the first object in +ary+ such that the object is - * <code>==</code> to +obj+. + * 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: * - * If a block is given instead of an argument, returns the _index_ of the - * first object for which the block returns +true+. Returns +nil+ if no - * match is found. + * a = [:foo, 'bar', 2, 'bar'] + * a.index {|element| element == 'bar' } # => 1 * - * See also Array#rindex. + * Returns +nil+ if the block never returns a truthy value. * - * An Enumerator is returned if neither a block nor argument is given. + * When neither an argument nor a block is given, returns a new Enumerator: * - * a = [ "a", "b", "c" ] - * a.index("b") #=> 1 - * a.index("z") #=> nil - * a.index {|x| x == "b"} #=> 1 + * a = [:foo, 'bar', 2] + * e = a.index + * e # => #<Enumerator: [:foo, "bar", 2]:index> + * e.each {|element| element == 'bar' } # => 1 + * + * Array#find_index is an alias for Array#index. + * + * Related: #rindex. */ static VALUE @@ -1725,49 +2235,58 @@ 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; } /* * call-seq: - * ary.rindex(obj) -> int or nil - * ary.rindex {|item| block} -> int or nil - * ary.rindex -> Enumerator + * array.rindex(object) -> integer or nil + * array.rindex {|element| ... } -> integer or nil + * array.rindex -> new_enumerator + * + * 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: * - * Returns the _index_ of the last object in +self+ <code>==</code> to +obj+. + * a = [:foo, 'bar', 2, 'bar'] + * a.rindex('bar') # => 3 * - * If a block is given instead of an argument, returns the _index_ of the - * first object for which the block returns +true+, starting from the last - * object. + * Returns +nil+ if no such object found. * - * Returns +nil+ if no match is found. + * 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: * - * See also Array#index. + * a = [:foo, 'bar', 2, 'bar'] + * a.rindex {|element| element == 'bar' } # => 3 * - * If neither block nor argument is given, an Enumerator is returned instead. + * Returns +nil+ if the block never returns a truthy value. * - * a = [ "a", "b", "b", "b", "c" ] - * a.rindex("b") #=> 3 - * a.rindex("z") #=> nil - * a.rindex {|x| x == "b"} #=> 3 + * When neither an argument nor a block is given, returns a new \Enumerator: + * + * a = [:foo, 'bar', 2, 'bar'] + * e = a.rindex + * e # => #<Enumerator: [:foo, "bar", 2, "bar"]:rindex> + * e.each {|element| element == 'bar' } # => 3 + * + * Related: #index. */ static VALUE @@ -1777,25 +2296,28 @@ 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; + } } return Qnil; } @@ -1818,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); @@ -1875,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)); - } + } } } @@ -1886,22 +2408,14 @@ 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); } -/*! - * expands or shrinks \a ary to \a len elements. - * expanded region will be filled with Qnil. - * \param ary an array - * \param len new size - * \return \a ary - * \post the size of \a ary is \a len. - */ VALUE rb_ary_resize(VALUE ary, long len) { @@ -1911,118 +2425,231 @@ 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) { - ary_heap_realloc(ary, len); - ARY_SET_CAPA(ary, len); - } - ARY_SET_HEAP_LEN(ary, len); + 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_verify(ary); return ary; } +static VALUE +ary_aset_by_rb_ary_store(VALUE ary, long key, VALUE val) +{ + rb_ary_store(ary, key, val); + return val; +} + +static VALUE +ary_aset_by_rb_ary_splice(VALUE ary, long beg, long len, VALUE val) +{ + VALUE rpl = rb_ary_to_ary(val); + rb_ary_splice(ary, beg, len, RARRAY_CONST_PTR_TRANSIENT(rpl), RARRAY_LEN(rpl)); + RB_GC_GUARD(rpl); + return val; +} + /* * call-seq: - * ary[index] = obj -> obj - * ary[start, length] = obj or other_ary or nil -> obj or other_ary or nil - * ary[range] = obj or other_ary or nil -> obj or other_ary or nil - * - * Element Assignment --- Sets the element at +index+, or replaces a subarray - * from the +start+ index for +length+ elements, or replaces a subarray - * specified by the +range+ of indices. - * - * If indices are greater than the current capacity of the array, the array - * grows automatically. Elements are inserted into the array at +start+ if - * +length+ is zero. - * - * Negative indices will count backward from the end of the array. For - * +start+ and +range+ cases the starting index is just before an element. - * - * An IndexError is raised if a negative index points past the beginning of - * the array. - * - * See also Array#push, and Array#unshift. - * - * a = Array.new - * a[4] = "4"; #=> [nil, nil, nil, nil, "4"] - * a[0, 3] = [ 'a', 'b', 'c' ] #=> ["a", "b", "c", nil, "4"] - * a[1..2] = [ 1, 2 ] #=> ["a", 1, 2, nil, "4"] - * a[0, 2] = "?" #=> ["?", 2, nil, "4"] - * a[0..2] = "A" #=> ["A", "4"] - * a[-1] = "Z" #=> ["A", "Z"] - * a[1..-1] = nil #=> ["A", nil] - * a[1..-1] = [] #=> ["A"] - * a[0, 0] = [ 1, 2 ] #=> [1, 2, "A"] - * a[3, 0] = "B" #=> [1, 2, "A", "B"] + * array[index] = object -> object + * array[start, length] = object -> object + * array[range] = object -> object + * + * Assigns elements in +self+; returns the given +object+. + * + * 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"] + * + * 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"] + * + * 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"] + * + * 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"] + * a = [:foo, 'bar', 2] + * a[1..-2] = 'foo' # => "foo" + * a # => [:foo, "foo", 2] + * a = [:foo, 'bar', 2] + * a[1..-3] = 'foo' # => "foo" + * a # => [:foo, "foo", "bar", 2] + * a = [:foo, 'bar', 2] + * + * 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 rb_ary_aset(int argc, VALUE *argv, VALUE ary) { long offset, beg, len; - VALUE rpl; + rb_check_arity(argc, 2, 3); + rb_ary_modify_check(ary); if (argc == 3) { - rb_ary_modify_check(ary); - beg = NUM2LONG(argv[0]); - len = NUM2LONG(argv[1]); - goto range; + beg = NUM2LONG(argv[0]); + len = NUM2LONG(argv[1]); + return ary_aset_by_rb_ary_splice(ary, beg, len, argv[2]); } - rb_check_arity(argc, 2, 2); - rb_ary_modify_check(ary); if (FIXNUM_P(argv[0])) { - offset = FIX2LONG(argv[0]); - goto fixnum; + 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 */ - range: - rpl = rb_ary_to_ary(argv[argc-1]); - rb_ary_splice(ary, beg, len, RARRAY_CONST_PTR_TRANSIENT(rpl), RARRAY_LEN(rpl)); - RB_GC_GUARD(rpl); - return argv[argc-1]; + /* check if idx is Range */ + return ary_aset_by_rb_ary_splice(ary, beg, len, argv[1]); } offset = NUM2LONG(argv[0]); -fixnum: - rb_ary_store(ary, offset, argv[1]); - return argv[1]; + return ary_aset_by_rb_ary_store(ary, offset, argv[1]); } /* * call-seq: - * ary.insert(index, obj...) -> ary + * array.insert(index, *objects) -> self + * + * Inserts given +objects+ before or after the element at \Integer index +offset+; + * returns +self+. + * + * 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>): * - * Inserts the given values before the element with the given +index+. + * a = [:foo, 'bar', 2] + * a.insert(5, :bat, :bam) + * a # => [:foo, "bar", 2, nil, nil, :bat, :bam] * - * Negative indices count backwards from the end of the array, where +-1+ is - * the last element. If a negative index is used, the given values will be - * inserted after that element, so using an index of +-1+ will insert the - * values at the end of the array. + * Does nothing if no objects given: + * + * a = [:foo, 'bar', 2] + * a.insert(1) + * a.insert(50) + * a.insert(-50) + * a # => [:foo, "bar", 2] + * + * 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] * - * a = %w{ a b c d } - * a.insert(2, 99) #=> ["a", "b", 99, "c", "d"] - * a.insert(-2, 1, 2, 3) #=> ["a", "b", 99, "c", 1, 2, 3, "d"] */ static VALUE @@ -2035,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; @@ -2060,20 +2687,47 @@ ary_enum_length(VALUE ary, VALUE args, VALUE eobj) /* * call-seq: - * ary.each {|item| block} -> ary - * ary.each -> Enumerator + * array.each {|element| ... } -> self + * array.each -> Enumerator + * + * Iterates over array elements. * - * Calls the given block once for each element in +self+, passing that element - * as a parameter. Returns the array itself. + * When a block given, passes each successive array element to the block; + * returns +self+: * - * If no block is given, an Enumerator is returned. + * a = [:foo, 'bar', 2] + * a.each {|element| puts "#{element.class} #{element}" } * - * a = [ "a", "b", "c" ] - * a.each {|x| print x, " -- " } + * Output: * - * produces: + * Symbol foo + * String bar + * Integer 2 * - * a -- b -- c -- + * 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 + * + * Related: #each_index, #reverse_each. */ VALUE @@ -2083,27 +2737,54 @@ 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; } /* * call-seq: - * ary.each_index {|index| block} -> ary - * ary.each_index -> Enumerator + * array.each_index {|index| ... } -> self + * array.each_index -> Enumerator + * + * Iterates over array indexes. + * + * When a block given, passes each successive array index to the block; + * returns +self+: * - * Same as Array#each, but passes the +index+ of the element instead of the - * element itself. + * a = [:foo, 'bar', 2] + * a.each_index {|index| puts "#{index} #{a[index]}" } * - * An Enumerator is returned if no block is given. + * Output: * - * a = [ "a", "b", "c" ] - * a.each_index {|x| print x, " -- " } + * 0 foo + * 1 bar + * 2 2 * - * produces: + * Allows the array to be modified during iteration: * - * 0 -- 1 -- 2 -- + * 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 + * + * Related: #each, #reverse_each. */ static VALUE @@ -2113,24 +2794,54 @@ 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; } /* * call-seq: - * ary.reverse_each {|item| block} -> ary - * ary.reverse_each -> Enumerator + * array.reverse_each {|element| ... } -> self + * array.reverse_each -> Enumerator + * + * Iterates backwards over array elements. + * + * 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: * - * Same as Array#each, but traverses +self+ in reverse order. + * Integer 2 + * String bar + * Symbol foo * - * a = [ "a", "b", "c" ] - * a.reverse_each {|x| print x, " " } + * Allows the array to be modified during iteration: * - * produces: + * a = [:foo, 'bar', 2] + * a.reverse_each {|element| puts element; a.clear if element.to_s.start_with?('b') } * - * c b a + * 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 + * + * Related: #each, #each_index. */ static VALUE @@ -2141,24 +2852,21 @@ 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; } /* * call-seq: - * ary.length -> int - * - * Returns the number of elements in +self+. May be zero. + * array.length -> an_integer * - * [ 1, 2, 3, 4, 5 ].length #=> 5 - * [].length #=> 0 + * Returns the count of elements in +self+. */ static VALUE @@ -2170,19 +2878,16 @@ rb_ary_length(VALUE ary) /* * call-seq: - * ary.empty? -> true or false - * - * Returns +true+ if +self+ contains no elements. + * array.empty? -> true or false * - * [].empty? #=> true + * Returns +true+ if the count of elements in +self+ is zero, + * +false+ otherwise. */ static VALUE rb_ary_empty_p(VALUE ary) { - if (RARRAY_LEN(ary) == 0) - return Qtrue; - return Qfalse; + return RBOOL(RARRAY_LEN(ary) == 0); } VALUE @@ -2218,15 +2923,15 @@ 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; } -static void +static long ary_join_0(VALUE ary, VALUE sep, long max, VALUE result) { long i; @@ -2234,11 +2939,40 @@ 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); - if (i > 0 && !NIL_P(sep)) - rb_str_buf_append(result, sep); - rb_str_buf_append(result, val); - if (OBJ_TAINTED(val)) OBJ_TAINT(result); + 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); + } + return i; +} + +static void +ary_join_1_str(VALUE dst, VALUE src, int *first) +{ + rb_str_buf_append(dst, src); + if (*first) { + rb_enc_copy(dst, src); + *first = FALSE; + } +} + +static void +ary_join_1_ary(VALUE obj, VALUE ary, VALUE sep, VALUE result, VALUE val, int *first) +{ + if (val == ary) { + rb_raise(rb_eArgError, "recursive array join"); + } + else { + VALUE args[4]; + + *first = FALSE; + args[0] = val; + args[1] = sep; + args[2] = result; + args[3] = (VALUE)first; + rb_exec_recursive(recursive_join, obj, (VALUE)args); } } @@ -2248,50 +2982,25 @@ 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); - - val = RARRAY_AREF(ary, i); - if (RB_TYPE_P(val, T_STRING)) { - str_join: - rb_str_buf_append(result, val); - if (*first) { - rb_enc_copy(result, val); - *first = FALSE; - } - } - else if (RB_TYPE_P(val, T_ARRAY)) { - obj = val; - ary_join: - if (val == ary) { - rb_raise(rb_eArgError, "recursive array join"); - } - else { - VALUE args[4]; - - *first = FALSE; - args[0] = val; - args[1] = sep; - args[2] = result; - args[3] = (VALUE)first; - rb_exec_recursive(recursive_join, obj, (VALUE)args); - } - } - else { - tmp = rb_check_string_type(val); - if (!NIL_P(tmp)) { - val = tmp; - goto str_join; - } - tmp = rb_check_array_type(val); - if (!NIL_P(tmp)) { - obj = val; - val = tmp; - goto ary_join; - } - val = rb_obj_as_string(val); - goto str_join; - } + if (i > 0 && !NIL_P(sep)) + rb_str_buf_append(result, sep); + + 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)) { + 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); + } + else if (!NIL_P(tmp = rb_check_array_type(val))) { + ary_join_1_ary(val, ary, sep, result, tmp, first); + } + else { + ary_join_1_str(result, rb_obj_as_string(val), first); + } } } @@ -2299,36 +3008,36 @@ VALUE rb_ary_join(VALUE ary, VALUE sep) { long len = 1, i; - int taint = FALSE; VALUE val, tmp, result; if (RARRAY_LEN(ary) == 0) return rb_usascii_str_new(0, 0); - if (OBJ_TAINTED(ary)) taint = TRUE; 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); - - if (NIL_P(tmp) || tmp != val) { - int first; - result = rb_str_buf_new(len + (RARRAY_LEN(ary)-i)*10); - rb_enc_associate(result, rb_usascii_encoding()); - if (taint) OBJ_TAINT(result); - ary_join_0(ary, sep, i, result); - first = i == 0; - ary_join_1(ary, ary, sep, i, result, &first); - return result; - } + val = RARRAY_AREF(ary, i); + tmp = rb_check_string_type(val); + + 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()); + i = ary_join_0(ary, sep, i, 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_buf_new(len); - if (taint) OBJ_TAINT(result); + result = rb_str_new(0, len); + rb_str_set_len(result, 0); + ary_join_0(ary, sep, RARRAY_LEN(ary), result); return result; @@ -2336,32 +3045,42 @@ rb_ary_join(VALUE ary, VALUE sep) /* * call-seq: - * ary.join(separator=$,) -> str + * array.join ->new_string + * array.join(separator = $,) -> new_string + * + * Returns the new \String formed by joining the array elements after conversion. + * 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>: * - * Returns a string created by converting each element of the array to - * a string, separated by the given +separator+. - * If the +separator+ is +nil+, it uses current <code>$,</code>. - * If both the +separator+ and <code>$,</code> are +nil+, - * it uses an empty string. + * a = [:foo, 'bar', 2] + * $, # => nil + * a.join # => "foobar2" * - * [ "a", "b", "c" ].join #=> "abc" - * [ "a", "b", "c" ].join("-") #=> "a-b-c" + * With \string argument +separator+, joins using that separator: * - * For nested arrays, join is applied recursively: + * a = [:foo, 'bar', 2] + * a.join("\n") # => "foo\nbar\n2" + * + * Joins recursively for nested Arrays: + * + * a = [:foo, [:bar, [:baz, :bat]]] + * a.join # => "foobarbazbat" * - * [ "a", [1, 2, [:x, :y]], "b" ].join("-") #=> "a-1-2-x-y-b" */ - static VALUE rb_ary_join_m(int argc, VALUE *argv, VALUE ary) { VALUE sep; - if (rb_check_arity(argc, 0, 1) == 0) { - sep = rb_output_fs; - } - else if (NIL_P(sep = argv[0])) { + if (rb_check_arity(argc, 0, 1) == 0 || NIL_P(sep = argv[0])) { sep = rb_output_fs; + if (!NIL_P(sep)) { + rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, "$, is set to non-nil value"); + } } return rb_ary_join(ary, sep); @@ -2370,32 +3089,32 @@ rb_ary_join_m(int argc, VALUE *argv, VALUE ary) static VALUE inspect_ary(VALUE ary, VALUE dummy, int recur) { - int tainted = OBJ_TAINTED(ary); long i; VALUE s, str; 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 (OBJ_TAINTED(s)) tainted = TRUE; - 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, "]"); - if (tainted) OBJ_TAINT(str); return str; } /* * call-seq: - * ary.inspect -> string - * ary.to_s -> string + * array.inspect -> new_string + * + * Returns the new \String formed by calling method <tt>#inspect</tt> + * on each array element: * - * Creates a string representation of +self+. + * a = [:foo, 'bar', 2] + * a.inspect # => "[:foo, \"bar\", 2]" * - * [ "a", "b", "c" ].to_s #=> "[\"a\", \"b\", \"c\"]" + * Array#to_s is an alias for Array#inspect. */ static VALUE @@ -2413,40 +3132,59 @@ rb_ary_to_s(VALUE ary) /* * call-seq: - * ary.to_a -> ary + * to_a -> self or new_array * - * Returns +self+. + * 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 + * a.kind_of?(Array) # => true + * a1 = a.to_a + * a1 # => ["foo", "bar", "two"] + * a1.class # => Array # Not MyArray * - * If called on a subclass of Array, converts the receiver to an Array object. */ 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; } /* * call-seq: - * ary.to_h -> hash - * ary.to_h { block } -> hash + * array.to_h -> new_hash + * array.to_h {|item| ... } -> new_hash + * + * Returns a new \Hash formed from +self+. + * + * 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: * - * Returns the result of interpreting <i>ary</i> as an array of - * <tt>[key, value]</tt> pairs. + * 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}} * - * [[:foo, :bar], [1, 2]].to_h - * # => {:foo => :bar, 1 => 2} + * 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: * - * If a block is given, the results of the block on each element of - * the array will be used as pairs. + * [].to_h # => {} + * a = [['foo', 'zero'], ['bar', 'one'], ['baz', 'two']] + * h = a.to_h + * h # => {"foo"=>"zero", "bar"=>"one", "baz"=>"two"} * - * ["foo", "bar"].to_h {|s| [s.ord, s]} - * # => {102=>"foo", 98=>"bar"} */ static VALUE @@ -2457,25 +3195,25 @@ 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; } /* * call-seq: - * ary.to_ary -> ary + * array.to_ary -> self * * Returns +self+. */ @@ -2490,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; } } @@ -2507,20 +3245,20 @@ 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; } /* * call-seq: - * ary.reverse! -> ary + * array.reverse! -> self * - * Reverses +self+ in place. + * Reverses +self+ in place: + * + * a = ['foo', 'bar', 'two'] + * a.reverse! # => ["two", "bar", "foo"] * - * a = [ "a", "b", "c" ] - * a.reverse! #=> ["c", "b", "a"] - * a #=> ["c", "b", "a"] */ static VALUE @@ -2531,12 +3269,14 @@ rb_ary_reverse_bang(VALUE ary) /* * call-seq: - * ary.reverse -> new_ary + * array.reverse -> new_array + * + * Returns a new \Array with the elements of +self+ in reverse order: * - * Returns a new array containing +self+'s elements in reverse order. + * a = ['foo', 'bar', 'two'] + * a1 = a.reverse + * a1 # => ["two", "bar", "foo"] * - * [ "a", "b", "c" ].reverse #=> ["c", "b", "a"] - * [ 1 ].reverse #=> [1] */ static VALUE @@ -2548,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; @@ -2563,10 +3303,22 @@ rotate_count(long cnt, long len) static void ary_rotate_ptr(VALUE *ptr, long len, long cnt) { - --len; - if (cnt < len) ary_reverse(ptr + cnt, ptr + len); - if (--cnt > 0) ary_reverse(ptr, ptr + cnt); - if (len > 0) ary_reverse(ptr, ptr + len); + if (cnt == 1) { + VALUE tmp = *ptr; + memmove(ptr, ptr + 1, sizeof(VALUE)*(len - 1)); + *(ptr + len - 1) = tmp; + } + else if (cnt == len - 1) { + VALUE tmp = *(ptr + len - 1); + memmove(ptr + 1, ptr, sizeof(VALUE)*(len - 1)); + *ptr = tmp; + } + else { + --len; + if (cnt < len) ary_reverse(ptr + cnt, ptr + len); + if (--cnt > 0) ary_reverse(ptr, ptr + cnt); + if (len > 0) ary_reverse(ptr, ptr + len); + } } VALUE @@ -2576,7 +3328,7 @@ rb_ary_rotate(VALUE ary, long cnt) if (cnt != 0) { long len = RARRAY_LEN(ary); - if (len > 0 && (cnt = rotate_count(cnt, len)) > 0) { + if (len > 1 && (cnt = rotate_count(cnt, len)) > 0) { RARRAY_PTR_USE_TRANSIENT(ary, ptr, ary_rotate_ptr(ptr, len, cnt)); return ary; } @@ -2586,19 +3338,48 @@ rb_ary_rotate(VALUE ary, long cnt) /* * call-seq: - * ary.rotate!(count=1) -> ary + * array.rotate! -> self + * array.rotate!(count) -> self * - * Rotates +self+ in place so that the element at +count+ comes first, and - * returns +self+. + * 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 negative then it rotates in the opposite direction, starting - * from the end of the array where +-1+ is the last element. + * 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] * - * a = [ "a", "b", "c", "d" ] - * a.rotate! #=> ["b", "c", "d", "a"] - * a #=> ["b", "c", "d", "a"] - * a.rotate!(2) #=> ["d", "a", "b", "c"] - * a.rotate!(-3) #=> ["a", "b", "c", "d"] */ static VALUE @@ -2611,19 +3392,51 @@ rb_ary_rotate_bang(int argc, VALUE *argv, VALUE ary) /* * call-seq: - * ary.rotate(count=1) -> new_ary + * array.rotate -> new_array + * array.rotate(count) -> new_array + * + * Returns a new \Array formed from +self+ with elements + * rotated from one end to the other. + * + * 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] * - * Returns a new array by rotating +self+ so that the element at +count+ is - * the first element of the new array. + * When given a non-negative \Integer +count+, + * returns a new \Array with +count+ elements rotated from the beginning to the end: * - * If +count+ is negative then it rotates in the opposite direction, starting - * from the end of +self+ where +-1+ is the last element. + * 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] * - * a = [ "a", "b", "c", "d" ] - * a.rotate #=> ["b", "c", "d", "a"] - * a #=> ["a", "b", "c", "d"] - * a.rotate(2) #=> ["c", "d", "a", "b"] - * a.rotate(-3) #=> ["b", "c", "d", "a"] */ static VALUE @@ -2637,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; @@ -2649,18 +3462,27 @@ rb_ary_rotate_m(int argc, VALUE *argv, VALUE ary) struct ary_sort_data { VALUE ary; - struct cmp_opt_data cmp_opt; + VALUE receiver; }; static VALUE sort_reentered(VALUE ary) { if (RBASIC(ary)->klass) { - rb_raise(rb_eRuntimeError, "sort reentered"); + rb_raise(rb_eRuntimeError, "sort reentered"); } return Qnil; } +static void +sort_returned(struct ary_sort_data *data) +{ + if (rb_obj_frozen_p(data->receiver)) { + rb_raise(rb_eFrozenError, "array frozen during sort"); + } + sort_reentered(data->ary); +} + static int sort_1(const void *ap, const void *bp, void *dummy) { @@ -2674,7 +3496,7 @@ sort_1(const void *ap, const void *bp, void *dummy) args[1] = b; retval = rb_yield_values2(2, args); n = rb_cmpint(retval, a, b); - sort_reentered(data->ary); + sort_returned(data); return n; } @@ -2686,47 +3508,64 @@ 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, Fixnum)) { - 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); n = rb_cmpint(retval, a, b); - sort_reentered(data->ary); + sort_returned(data); return n; } /* * call-seq: - * ary.sort! -> ary - * ary.sort! {|a, b| block} -> ary + * array.sort! -> self + * array.sort! {|a, b| ... } -> self + * + * Returns +self+ with its elements sorted in place. * - * Sorts +self+ in place. + * With no block, compares elements using operator <tt><=></tt> + * (see Comparable): * - * Comparisons for the sort will be done using the <code><=></code> operator - * or using an optional code block. + * a = 'abcde'.split('').shuffle + * a # => ["e", "b", "d", "a", "c"] + * a.sort! + * a # => ["a", "b", "c", "d", "e"] * - * The block must implement a comparison between +a+ and +b+ and return - * an integer less than 0 when +b+ follows +a+, +0+ when +a+ and +b+ - * are equivalent, or an integer greater than 0 when +a+ follows +b+. + * With a block, calls the block with each element pair; + * for each element pair +a+ and +b+, the block should return an integer: * - * The result is not guaranteed to be stable. When the comparison of two - * elements returns +0+, the order of the elements is unpredictable. + * - Negative when +b+ is to follow +a+. + * - Zero when +a+ and +b+ are equivalent. + * - Positive when +a+ is to follow +b+. * - * ary = [ "d", "a", "e", "c", "b" ] - * ary.sort! #=> ["a", "b", "c", "d", "e"] - * ary.sort! {|a, b| b <=> a} #=> ["e", "d", "c", "b", "a"] + * Example: + * + * a = 'abcde'.split('').shuffle + * a # => ["e", "b", "d", "a", "c"] + * a.sort! {|a, b| a <=> b } + * a # => ["a", "b", "c", "d", "e"] + * a.sort! {|a, b| b <=> a } + * a # => ["e", "d", "c", "b", "a"] + * + * 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"] * - * See also Enumerable#sort_by. */ VALUE @@ -2735,24 +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; - data.cmp_opt.opt_methods = 0; - data.cmp_opt.opt_inited = 0; - RARRAY_PTR_USE(tmp, ptr, { + 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; + 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 { @@ -2791,26 +3629,44 @@ rb_ary_sort_bang(VALUE ary) /* * call-seq: - * ary.sort -> new_ary - * ary.sort {|a, b| block} -> new_ary + * array.sort -> new_array + * array.sort {|a, b| ... } -> new_array * - * Returns a new array created by sorting +self+. + * Returns a new \Array whose elements are those from +self+, sorted. * - * Comparisons for the sort will be done using the <code><=></code> operator - * or using an optional code block. + * With no block, compares elements using operator <tt><=></tt> + * (see Comparable): * - * The block must implement a comparison between +a+ and +b+ and return - * an integer less than 0 when +b+ follows +a+, +0+ when +a+ and +b+ - * are equivalent, or an integer greater than 0 when +a+ follows +b+. + * a = 'abcde'.split('').shuffle + * a # => ["e", "b", "d", "a", "c"] + * a1 = a.sort + * a1 # => ["a", "b", "c", "d", "e"] * - * The result is not guaranteed to be stable. When the comparison of two - * elements returns +0+, the order of the elements is unpredictable. + * With a block, calls the block with each element pair; + * for each element pair +a+ and +b+, the block should return an integer: * - * ary = [ "d", "a", "e", "c", "b" ] - * ary.sort #=> ["a", "b", "c", "d", "e"] - * ary.sort {|a, b| b <=> a} #=> ["e", "d", "c", "b", "a"] + * - Negative when +b+ is to follow +a+. + * - Zero when +a+ and +b+ are equivalent. + * - Positive when +a+ is to follow +b+. * - * See also Enumerable#sort_by. + * Example: + * + * a = 'abcde'.split('').shuffle + * a # => ["e", "b", "d", "a", "c"] + * a1 = a.sort {|a, b| a <=> b } + * a1 # => ["a", "b", "c", "d", "e"] + * a2 = a.sort {|a, b| b <=> a } + * a2 # => ["e", "d", "c", "b", "a"] + * + * 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 } + * a1 # => ["c", "e", "b", "d", "a"] + * + * Related: Enumerable#sort_by. */ VALUE @@ -2825,55 +3681,12 @@ static VALUE rb_ary_bsearch_index(VALUE ary); /* * call-seq: - * ary.bsearch {|x| block } -> elem - * - * By using binary search, finds a value from this array which meets - * the given condition in O(log n) where n is the size of the array. - * - * You can use this method in two modes: a find-minimum mode and - * a find-any mode. In either case, the elements of the array must be - * monotone (or sorted) with respect to the block. - * - * In find-minimum mode (this is a good choice for typical use cases), - * the block must always return true or false, and there must be an index i - * (0 <= i <= ary.size) so that: - * - * - the block returns false for any element whose index is less than - * i, and - * - the block returns true for any element whose index is greater - * than or equal to i. - * - * This method returns the i-th element. If i is equal to ary.size, - * it returns nil. - * - * ary = [0, 4, 7, 10, 12] - * ary.bsearch {|x| x >= 4 } #=> 4 - * ary.bsearch {|x| x >= 6 } #=> 7 - * ary.bsearch {|x| x >= -1 } #=> 0 - * ary.bsearch {|x| x >= 100 } #=> nil - * - * In find-any mode (this behaves like libc's bsearch(3)), the block - * must always return a number, and there must be two indices i and j - * (0 <= i <= j <= ary.size) so that: - * - * - the block returns a positive number for ary[k] if 0 <= k < i, - * - the block returns zero for ary[k] if i <= k < j, and - * - the block returns a negative number for ary[k] if - * j <= k < ary.size. - * - * Under this condition, this method returns any element whose index - * is within i...j. If i is equal to j (i.e., there is no element - * that satisfies the block), this method returns nil. - * - * ary = [0, 4, 7, 10, 12] - * # try to find v such that 4 <= v < 8 - * ary.bsearch {|x| 1 - x / 4 } #=> 4 or 7 - * # try to find v such that 8 <= v < 10 - * ary.bsearch {|x| 4 - x / 2 } #=> nil - * - * You must not mix the two modes at a time; the block must always - * return either true/false, or always return a number. It is - * undefined which value is actually picked up at each iteration. + * array.bsearch {|element| ... } -> object + * array.bsearch -> new_enumerator + * + * Returns an element from +self+ selected by a binary search. + * + * See {Binary Searching}[rdoc-ref:bsearch.rdoc]. */ static VALUE @@ -2882,22 +3695,18 @@ 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; } /* * call-seq: - * ary.bsearch_index {|x| block } -> int or nil + * array.bsearch_index {|element| ... } -> integer or nil + * array.bsearch_index -> new_enumerator * - * By using binary search, finds an index of a value from this array which - * meets the given condition in O(log n) where n is the size of the array. - * - * It supports two modes, depending on the nature of the block. They are - * exactly the same as in the case of the #bsearch method, with the only difference - * being that this method returns the index of the element instead of the - * element itself. For more details consult the documentation for #bsearch. + * Searches +self+ as described at method #bsearch, + * but returns the _index_ of the found element instead of the element itself. */ static VALUE @@ -2909,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 (v == Qfalse || v == Qnil) { - 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); @@ -2956,18 +3765,28 @@ sort_by_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, dummy)) /* * call-seq: - * ary.sort_by! {|obj| block} -> ary - * ary.sort_by! -> Enumerator + * array.sort_by! {|element| ... } -> self + * array.sort_by! -> new_enumerator + * + * Sorts the elements of +self+ in place, + * using an ordering determined by the block; returns self. + * + * Calls the block with each successive element; + * sorts elements based on the values returned from the block. + * + * For duplicates returned by the block, the ordering is indeterminate, and may be unstable. + * + * This example sorts strings based on their sizes: * - * Sorts +self+ in place using a set of keys generated by mapping the - * values in +self+ through the given block. + * a = ['aaaa', 'bbb', 'cc', 'd'] + * a.sort_by! {|element| element.size } + * a # => ["d", "cc", "bbb", "aaaa"] * - * The result is not guaranteed to be stable. When two keys are equal, - * the order of the corresponding elements is unpredictable. + * Returns a new \Enumerator if no block given: * - * If no block is given, an Enumerator is returned instead. + * a = ['aaaa', 'bbb', 'cc', 'd'] + * a.sort_by! # => #<Enumerator: ["aaaa", "bbb", "cc", "d"]:sort_by!> * - * See also Enumerable#sort_by. */ static VALUE @@ -2985,23 +3804,22 @@ rb_ary_sort_by_bang(VALUE ary) /* * call-seq: - * ary.collect {|item| block} -> new_ary - * ary.map {|item| block} -> new_ary - * ary.collect -> Enumerator - * ary.map -> Enumerator + * array.map {|element| ... } -> new_array + * array.map -> new_enumerator * - * Invokes the given block once for each element of +self+. + * Calls the block, if given, with each element of +self+; + * returns a new \Array whose elements are the return values from the block: * - * Creates a new array containing the values returned by the block. + * a = [:foo, 'bar', 2] + * a1 = a.map {|element| element.class } + * a1 # => [Symbol, String, Integer] * - * See also Enumerable#collect. + * Returns a new \Enumerator if no block given: + * a = [:foo, 'bar', 2] + * a1 = a.map + * a1 # => #<Enumerator: [:foo, "bar", 2]:map> * - * If no block is given, an Enumerator is returned instead. - * - * a = [ "a", "b", "c", "d" ] - * a.collect {|x| x + "!"} #=> ["a!", "b!", "c!", "d!"] - * a.map.with_index {|x, i| x * i} #=> ["", "b", "cc", "ddd"] - * a #=> ["a", "b", "c", "d"] + * Array#collect is an alias for Array#map. */ static VALUE @@ -3021,23 +3839,22 @@ rb_ary_collect(VALUE ary) /* * call-seq: - * ary.collect! {|item| block } -> ary - * ary.map! {|item| block } -> ary - * ary.collect! -> Enumerator - * ary.map! -> Enumerator + * array.map! {|element| ... } -> self + * array.map! -> new_enumerator + * + * Calls the block, if given, with each element; + * replaces the element with the block's return value: * - * Invokes the given block once for each element of +self+, replacing the - * element with the value returned by the block. + * a = [:foo, 'bar', 2] + * a.map! { |element| element.class } # => [Symbol, String, Integer] * - * See also Enumerable#collect. + * Returns a new \Enumerator if no block given: * - * If no block is given, an Enumerator is returned instead. + * a = [:foo, 'bar', 2] + * a1 = a.map! + * a1 # => #<Enumerator: [:foo, "bar", 2]:map!> * - * a = [ "a", "b", "c", "d" ] - * a.map! {|x| x + "!" } - * a #=> [ "a!", "b!", "c!", "d!" ] - * a.collect!.with_index {|x, i| x[0...i] } - * a #=> ["", "b", "c!", "d!"] + * Array#collect! is an alias for Array#map!. */ static VALUE @@ -3048,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; } @@ -3060,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; } @@ -3084,45 +3901,70 @@ 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)); } /* * call-seq: - * ary.values_at(selector, ...) -> new_ary + * array.values_at(*indexes) -> new_array + * + * Returns a new \Array whose elements are the elements + * of +self+ at the given \Integer or \Range +indexes+. + * + * For each positive +index+, returns the element at offset +index+: * - * Returns an array containing the elements in +self+ corresponding to the - * given +selector+(s). + * a = [:foo, 'bar', 2] + * a.values_at(0, 2) # => [:foo, 2] + * a.values_at(0..1) # => [:foo, "bar"] * - * The selectors may be either integer indices or ranges. + * The given +indexes+ may be in any order, and may repeat: * - * See also Array#select. + * 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] * - * a = %w{ a b c d e f } - * a.values_at(1, 3, 5) # => ["b", "d", "f"] - * a.values_at(1, 3, 5, 7) # => ["b", "d", "f", nil] - * a.values_at(-1, -2, -2, -7) # => ["f", "e", "e", nil] - * a.values_at(4..6, 3...6) # => ["e", "f", nil, "d", "e", "f"] */ static VALUE @@ -3131,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; @@ -3140,22 +3982,21 @@ rb_ary_values_at(int argc, VALUE *argv, VALUE ary) /* * call-seq: - * ary.select {|item| block} -> new_ary - * ary.select -> Enumerator - * ary.filter {|item| block} -> new_ary - * ary.filter -> Enumerator - * - * Returns a new array containing all elements of +ary+ - * for which the given +block+ returns a true value. + * array.select {|element| ... } -> new_array + * array.select -> new_enumerator * - * If no block is given, an Enumerator is returned instead. + * 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: * - * [1,2,3,4,5].select {|num| num.even? } #=> [2, 4] + * a = [:foo, 'bar', 2, :bam] + * a1 = a.select {|element| element.to_s.start_with?('b') } + * a1 # => ["bar", :bam] * - * a = %w[ a b c d e f ] - * a.select {|v| v =~ /[aeiou]/ } #=> ["a", "e"] + * Returns a new \Enumerator if no block given: * - * See also Enumerable#select. + * a = [:foo, 'bar', 2, :bam] + * a.select # => #<Enumerator: [:foo, "bar", 2, :bam]:select> * * Array#filter is an alias for Array#select. */ @@ -3169,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; } @@ -3189,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; } @@ -3208,35 +4049,38 @@ select_bang_ensure(VALUE a) long i1 = arg->len[0], i2 = arg->len[1]; if (i2 < len && i2 < i1) { - long tail = 0; - if (i1 < len) { - tail = len - i1; + long tail = 0; + rb_ary_modify(ary); + 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; } /* * call-seq: - * ary.select! {|item| block } -> ary or nil - * ary.select! -> Enumerator - * ary.filter! {|item| block } -> ary or nil - * ary.filter! -> Enumerator + * array.select! {|element| ... } -> self or nil + * array.select! -> new_enumerator + * + * Calls the block, if given with each element of +self+; + * removes from +self+ those elements for which the block returns +false+ or +nil+. * - * Invokes the given block passing in successive elements from +self+, - * deleting elements for which the block returns a +false+ value. + * Returns +self+ if any elements were removed: * - * The array may not be changed instantly every time the block is called. + * a = [:foo, 'bar', 2, :bam] + * a.select! {|element| element.to_s.start_with?('b') } # => ["bar", :bam] * - * If changes were made, it will return +self+, otherwise it returns +nil+. + * Returns +nil+ if no elements were removed. * - * If no block is given, an Enumerator is returned instead. + * Returns a new \Enumerator if no block given: * - * See also Array#keep_if. + * a = [:foo, 'bar', 2, :bam] + * a.select! # => #<Enumerator: [:foo, "bar", 2, :bam]:select!> * * Array#filter! is an alias for Array#select!. */ @@ -3256,19 +4100,20 @@ rb_ary_select_bang(VALUE ary) /* * call-seq: - * ary.keep_if {|item| block} -> ary - * ary.keep_if -> Enumerator + * array.keep_if {|element| ... } -> self + * array.keep_if -> new_enumeration + * + * Retains those elements for which the block returns a truthy value; + * deletes all other elements; returns +self+: * - * Deletes every element of +self+ for which the given block evaluates to - * +false+, and returns +self+. + * a = [:foo, 'bar', 2, :bam] + * a.keep_if {|element| element.to_s.start_with?('b') } # => ["bar", :bam] * - * If no block is given, an Enumerator is returned instead. + * Returns a new \Enumerator if no block given: * - * a = %w[ a b c d e f ] - * a.keep_if {|v| v =~ /[aeiou]/ } #=> ["a", "e"] - * a #=> ["a", "e"] + * a = [:foo, 'bar', 2, :bam] + * a.keep_if # => #<Enumerator: [:foo, "bar", 2, :bam]:keep_if> * - * See also Array#select!. */ static VALUE @@ -3284,32 +4129,48 @@ 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); + } } } /* * call-seq: - * ary.delete(obj) -> item or nil - * ary.delete(obj) {block} -> item or result of block + * array.delete(obj) -> deleted_object + * array.delete(obj) {|nosuch| ... } -> deleted_object or block_return + * + * 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" + * a # => [:foo, 2] + * + * Returns +nil+ if no elements removed. * - * Deletes all items from +self+ that are equal to +obj+. + * When a block is given, + * removes from +self+ each element +ele+ such that <tt>ele == obj</tt>. * - * Returns the last deleted item, or +nil+ if no matching item is found. + * If any such elements are found, ignores the block + * and returns the last deleted element: * - * If the optional code block is given, the result of the block is returned if - * the item is not found. (To remove +nil+ elements and get an informative - * return value, use Array#compact!) + * 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" * - * a = [ "a", "b", "b", "b", "c" ] - * a.delete("b") #=> "b" - * a #=> ["a", "c"] - * a.delete("z") #=> nil - * a.delete("z") {"not found"} #=> "not found" */ VALUE @@ -3319,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); @@ -3349,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); @@ -3374,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); @@ -3390,17 +4251,25 @@ rb_ary_delete_at(VALUE ary, long pos) /* * call-seq: - * ary.delete_at(index) -> obj or nil + * array.delete_at(index) -> deleted_object or nil + * + * Deletes an element from +self+, per the given \Integer +index+. * - * Deletes the element at the specified +index+, returning that element, or - * +nil+ if the +index+ is out of range. + * When +index+ is non-negative, deletes the element at offset +index+: * - * See also Array#slice! + * a = [:foo, 'bar', 2] + * a.delete_at(1) # => "bar" + * a # => [:foo, 2] * - * a = ["ant", "bat", "cat", "dog"] - * a.delete_at(2) #=> "cat" - * a #=> ["ant", "bat", "dog"] - * a.delete_at(99) #=> nil + * 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] + * + * If +index+ is too small (far from zero), returns nil. */ static VALUE @@ -3409,70 +4278,133 @@ rb_ary_delete_at_m(VALUE ary, VALUE pos) return rb_ary_delete_at(ary, NUM2LONG(pos)); } +static VALUE +ary_slice_bang_by_rb_ary_splice(VALUE ary, long pos, long len) +{ + const long orig_len = RARRAY_LEN(ary); + + if (len < 0) { + return Qnil; + } + else if (pos < -orig_len) { + return Qnil; + } + else if (pos < 0) { + pos += orig_len; + } + else if (orig_len < pos) { + return Qnil; + } + if (orig_len < pos + len) { + len = orig_len - pos; + } + if (len == 0) { + return rb_ary_new2(0); + } + else { + VALUE arg2 = rb_ary_new4(len, RARRAY_CONST_PTR_TRANSIENT(ary)+pos); + rb_ary_splice(ary, pos, len, 0, 0); + return arg2; + } +} + /* * call-seq: - * ary.slice!(index) -> obj or nil - * ary.slice!(start, length) -> new_ary or nil - * ary.slice!(range) -> new_ary or nil - * - * Deletes the element(s) given by an +index+ (optionally up to +length+ - * elements) or by a +range+. - * - * Returns the deleted object (or objects), or +nil+ if the +index+ is out of - * range. - * - * a = [ "a", "b", "c" ] - * a.slice!(1) #=> "b" - * a #=> ["a", "c"] - * a.slice!(-1) #=> "c" - * a #=> ["a"] - * a.slice!(100) #=> nil - * a #=> ["a"] + * array.slice!(n) -> object or nil + * array.slice!(start, length) -> new_array or nil + * array.slice!(range) -> new_array or nil + * + * Removes and returns elements from +self+. + * + * 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"] + * + * If +n+ is out of range, returns +nil+. + * + * 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: + * + * 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] + * + * If <tt>start == a.size</tt> and +length+ is non-negative, + * returns a new empty \Array. + * + * If +length+ is negative, returns +nil+. + * + * 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 # => [:foo] + * + * If <tt>range.start == a.size</tt>, returns a new empty \Array. + * + * 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 rb_ary_slice_bang(int argc, VALUE *argv, VALUE ary) { - VALUE arg1, arg2; - long pos, len, orig_len; + VALUE arg1; + long pos, len; rb_ary_modify_check(ary); - if (argc == 2) { - pos = NUM2LONG(argv[0]); - len = NUM2LONG(argv[1]); - delete_pos_len: - if (len < 0) return Qnil; - orig_len = RARRAY_LEN(ary); - if (pos < 0) { - pos += orig_len; - if (pos < 0) return Qnil; - } - else if (orig_len < pos) return Qnil; - if (orig_len < pos + len) { - len = orig_len - pos; - } - if (len == 0) return rb_ary_new2(0); - arg2 = rb_ary_new4(len, RARRAY_CONST_PTR_TRANSIENT(ary)+pos); - RBASIC_SET_CLASS(arg2, rb_obj_class(ary)); - rb_ary_splice(ary, pos, len, 0, 0); - return arg2; - } - rb_check_arity(argc, 1, 2); arg1 = argv[0]; + if (argc == 2) { + 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 */ - goto delete_pos_len; - case Qnil: - /* invalid range */ - return Qnil; - default: - /* not a range */ - break; - } + 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; + } } return rb_ary_delete_at(ary, NUM2LONG(arg1)); @@ -3484,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; } @@ -3501,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; } @@ -3523,17 +4455,23 @@ ary_reject_bang(VALUE ary) /* * call-seq: - * ary.reject! {|item| block} -> ary or nil - * ary.reject! -> Enumerator + * array.reject! {|element| ... } -> self or nil + * array.reject! -> new_enumerator + * + * Removes each element for which the block returns a truthy value. + * + * Returns +self+ if any elements removed: * - * Deletes every element of +self+ for which the block evaluates to +true+, - * if no changes were made returns +nil+. + * a = [:foo, 'bar', 2, 'bat'] + * a.reject! {|element| element.to_s.start_with?('b') } # => [:foo, 2] * - * The array may not be changed instantly every time the block is called. + * Returns +nil+ if no elements removed. * - * See also Enumerable#reject and Array#delete_if. + * Returns a new \Enumerator if no block given: + * + * a = [:foo, 'bar', 2] + * a.reject! # => #<Enumerator: [:foo, "bar", 2]:reject!> * - * If no block is given, an Enumerator is returned instead. */ static VALUE @@ -3546,15 +4484,21 @@ rb_ary_reject_bang(VALUE ary) /* * call-seq: - * ary.reject {|item| block } -> new_ary - * ary.reject -> Enumerator + * array.reject {|element| ... } -> new_array + * array.reject -> new_enumerator + * + * 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 array containing the items in +self+ for which the given - * block is not +true+. The ordering of non-rejected elements is maintained. + * Returns a new \Enumerator if no block given: * - * See also Array#delete_if + * a = [:foo, 'bar', 2] + * a.reject # => #<Enumerator: [:foo, "bar", 2]:reject> * - * If no block is given, an Enumerator is returned instead. */ static VALUE @@ -3570,21 +4514,21 @@ rb_ary_reject(VALUE ary) /* * call-seq: - * ary.delete_if {|item| block} -> ary - * ary.delete_if -> Enumerator + * array.delete_if {|element| ... } -> self + * array.delete_if -> Enumerator * - * Deletes every element of +self+ for which block evaluates to +true+. + * Removes each element in +self+ for which the block returns a truthy value; + * returns +self+: * - * The array is changed instantly every time the block is called, not after - * the iteration is over. + * a = [:foo, 'bar', 2, 'bat'] + * a.delete_if {|element| element.to_s.start_with?('b') } # => [:foo, 2] * - * See also Array#reject! + * Returns a new \Enumerator if no block given: * - * If no block is given, an Enumerator is returned instead. + * a = [:foo, 'bar', 2] + * a.delete_if # => #<Enumerator: [:foo, "bar", 2]:delete_if> * - * scores = [ 97, 42, 75 ] - * scores.delete_if {|score| score < 80 } #=> [97] - */ +3 */ static VALUE rb_ary_delete_if(VALUE ary) @@ -3599,10 +4543,9 @@ static VALUE take_i(RB_BLOCK_CALL_FUNC_ARGLIST(val, cbarg)) { VALUE *args = (VALUE *)cbarg; - if (args[1] == 0) rb_iter_break(); - else args[1]--; if (argc > 1) val = rb_ary_new4(argc, argv); rb_ary_push(args[0], val); + if (--args[1] == 0) rb_iter_break(); return Qnil; } @@ -3612,38 +4555,71 @@ take_items(VALUE obj, long n) VALUE result = rb_check_array_type(obj); VALUE args[2]; + if (n == 0) return result; 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; } /* * call-seq: - * ary.zip(arg, ...) -> new_ary - * ary.zip(arg, ...) {|arr| block} -> nil + * array.zip(*other_arrays) -> new_array + * array.zip(*other_arrays) {|other_array| ... } -> nil + * + * When no block given, returns a new \Array +new_array+ of size <tt>self.size</tt> + * whose elements are Arrays. + * + * 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] + * d = a.zip(b, c) + * d # => [[:a0, :b0, :c0], [:a1, :b1, :c1], [:a2, :b2, :c2], [:a3, :b3, :c3]] + * + * 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] + * d = a.zip(b, c) + * d # => [[:a0, :b0, :c0], [:a1, :b1, :c1], [:a2, :b2, nil], [:a3, nil, nil]] * - * Converts any arguments to arrays, then merges elements of +self+ with - * corresponding elements from each argument. + * If any array in +other_arrays+ is larger than +self+, + * its trailing elements are ignored: * - * This generates a sequence of <code>ary.size</code> _n_-element arrays, - * where _n_ is one more than the count of arguments. + * 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]] * - * If the size of any argument is less than the size of the initial array, - * +nil+ values are supplied. + * When a block is given, calls the block with each of the sub-arrays (formed as above); returns +nil+: * - * If a block is given, it is invoked for each output +array+, otherwise an - * array of arrays is returned. + * 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] * - * a = [ 4, 5, 6 ] - * b = [ 7, 8, 9 ] - * [1, 2, 3].zip(a, b) #=> [[1, 4, 7], [2, 5, 8], [3, 6, 9]] - * [1, 2].zip(a, b) #=> [[1, 4, 7], [2, 5, 8]] - * a.zip([1, 2], [8]) #=> [[4, 1, 8], [5, 2, nil], [6, nil, nil]] */ static VALUE @@ -3654,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; @@ -3706,15 +4682,14 @@ rb_ary_zip(int argc, VALUE *argv, VALUE ary) /* * call-seq: - * ary.transpose -> new_ary + * array.transpose -> new_array * - * Assumes that +self+ is an array of arrays and transposes the rows and - * columns. + * Transposes the rows and columns in an \Array of Arrays; + * the nested Arrays must all be the same size: * - * a = [[1,2], [3,4], [5,6]] - * a.transpose #=> [[1, 3, 5], [2, 4, 6]] + * a = [[:a0, :a1], [:b0, :b1], [:c0, :c1]] + * a.transpose # => [[:a0, :b0, :c0], [:a1, :b1, :c1]] * - * If the length of the subarrays don't match, an IndexError is raised. */ static VALUE @@ -3726,36 +4701,34 @@ 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; } /* * call-seq: - * ary.replace(other_ary) -> ary - * ary.initialize_copy(other_ary) -> ary + * array.replace(other_array) -> self * - * Replaces the contents of +self+ with the contents of +other_ary+, - * truncating or expanding if necessary. + * 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] * - * a = [ "a", "b", "c", "d", "e" ] - * a.replace([ "x", "y", "z" ]) #=> ["x", "y", "z"] - * a #=> ["x", "y", "z"] */ VALUE @@ -3765,35 +4738,39 @@ 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 = 0; + rb_ary_reset(copy); - if (ARY_OWNS_HEAP_P(copy)) { - ary_heap_free(copy); - } - else if (ARY_SHARED_P(copy)) { - shared = ARY_SHARED(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) { - rb_ary_decrement_share(shared); - } - 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 = ary_make_shared(orig); - if (ARY_OWNS_HEAP_P(copy)) { - ary_heap_free(copy); - } - else { - rb_ary_unshare_safe(copy); - } + VALUE shared_root = ary_make_shared(orig); FL_UNSET_EMBED(copy); ARY_SET_PTR(copy, ARY_HEAP_PTR(orig)); ARY_SET_LEN(copy, ARY_HEAP_LEN(orig)); - rb_ary_set_shared(copy, shared); + rb_ary_set_shared(copy, shared_root); } ary_verify(copy); return copy; @@ -3801,12 +4778,13 @@ rb_ary_replace(VALUE copy, VALUE orig) /* * call-seq: - * ary.clear -> ary + * array.clear -> self * - * Removes all elements from +self+. + * Removes all elements from +self+: + * + * a = [:foo, 'bar', 2] + * a.clear # => [] * - * a = [ "a", "b", "c", "d", "e" ] - * a.clear #=> [ ] */ VALUE @@ -3814,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); @@ -3832,32 +4810,198 @@ rb_ary_clear(VALUE ary) /* * call-seq: - * ary.fill(obj) -> ary - * ary.fill(obj, start [, length]) -> ary - * ary.fill(obj, range) -> ary - * ary.fill {|index| block} -> ary - * ary.fill(start [, length]) {|index| block} -> ary - * ary.fill(range) {|index| block} -> ary + * array.fill(obj) -> self + * array.fill(obj, start) -> self + * array.fill(obj, start, length) -> self + * array.fill(obj, range) -> self + * array.fill {|index| ... } -> self + * array.fill(start) {|index| ... } -> self + * array.fill(start, length) {|index| ... } -> self + * array.fill(range) {|index| ... } -> self + * + * 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] + * + * With arguments +obj+ and \Integer +start+, and no block given, + * replaces elements based on the given start. + * + * 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'] + * a.fill(:X, -50) # => [:X, :X, :X, :X] + * + * With arguments +obj+, \Integer +start+, and \Integer +length+, and no block given, + * 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"] + * + * With arguments +obj+ and \Range +range+, and no block given, + * replaces elements based on the given range. + * + * 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: * - * The first three forms set the selected elements of +self+ (which - * may be the entire array) to +obj+. + * a = ['a', 'b', 'c', 'd'] + * a.fill(:X, (0..-2)) # => [:X, :X, :X, "d"] + * a = ['a', 'b', 'c', 'd'] + * a.fill(:X, (1..-2)) # => ["a", :X, :X, "d"] * - * A +start+ of +nil+ is equivalent to zero. + * If <tt>range.last</tt> and <tt>range.last</tt> are both negative, + * both count from the end of the array: * - * A +length+ of +nil+ is equivalent to the length of the array. + * a = ['a', 'b', 'c', 'd'] + * a.fill(:X, (-1..-1)) # => ["a", "b", "c", :X] + * a = ['a', 'b', 'c', 'd'] + * a.fill(:X, (-2..-2)) # => ["a", "b", :X, "d"] * - * The last three forms fill the array with the value of the given block, - * which is passed the absolute index of each element to be filled. + * With no arguments and a block given, calls the block with each index; + * replaces the corresponding element with the block's return value: * - * Negative values of +start+ count from the end of the array, where +-1+ is - * the last element. + * 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. + * + * 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'] + * a.fill(-50) { |index| "new_#{index}" } # => ["new_0", "new_1", "new_2", "new_3"] + * + * With arguments +start+ and +length+, and a block given, + * calls the block for each index specified by start length; + * 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"] + * + * With arguments +obj+ and +range+, and a block given, + * calls the block with each index in the given range; + * replaces the corresponding element with the block's return value. + * + * 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'] + * a.fill(1..-2) { |index| "new_#{index}" } # => ["a", "new_1", "new_2", "d"] + * + * 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"] * - * a = [ "a", "b", "c", "d" ] - * a.fill("x") #=> ["x", "x", "x", "x"] - * a.fill("z", 2, 2) #=> ["x", "x", "z", "z"] - * a.fill("y", 0..1) #=> ["y", "y", "z", "z"] - * a.fill {|i| i*i} #=> [0, 1, 4, 9] - * a.fill(-2) {|i| i*i*i} #=> [0, 1, 8, 27] */ static VALUE @@ -3867,84 +5011,74 @@ 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; } /* * call-seq: - * ary + other_ary -> new_ary + * array + other_array -> new_array * - * Concatenation --- Returns a new array built by concatenating the - * two arrays together to produce a third array. + * Returns a new \Array containing all elements of +array+ + * followed by all elements of +other_array+: * - * [ 1, 2, 3 ] + [ 4, 5 ] #=> [ 1, 2, 3, 4, 5 ] - * a = [ "a", "b", "c" ] - * c = a + [ "d", "e", "f" ] - * c #=> [ "a", "b", "c", "d", "e", "f" ] - * a #=> [ "a", "b", "c" ] + * a = [0, 1] + [2, 3] + * a # => [0, 1, 2, 3] * - * Note that - * x += y - * is the same as - * x = x + y - * This means that it produces a new array. As a consequence, - * repeated use of <code>+=</code> on arrays can be quite inefficient. - * - * See also Array#concat. + * Related: #concat. */ VALUE @@ -3972,27 +5106,18 @@ ary_append(VALUE x, VALUE y) if (n > 0) { rb_ary_splice(x, RARRAY_LEN(x), 0, RARRAY_CONST_PTR_TRANSIENT(y), n); } + RB_GC_GUARD(y); return x; } /* * call-seq: - * ary.concat(other_ary1, other_ary2, ...) -> ary - * - * Appends the elements of <code>other_ary</code>s to +self+. + * array.concat(*other_arrays) -> self * - * [ "a", "b" ].concat( ["c", "d"]) #=> [ "a", "b", "c", "d" ] - * [ "a" ].concat( ["b"], ["c", "d"]) #=> [ "a", "b", "c", "d" ] - * [ "a" ].concat #=> [ "a" ] + * Adds to +array+ all elements from each \Array in +other_arrays+; returns +self+: * - * a = [ 1, 2, 3 ] - * a.concat( [ 4, 5 ]) - * a #=> [ 1, 2, 3, 4, 5 ] - * - * a = [ 1, 2 ] - * a.concat(a, a) #=> [1, 2, 1, 2, 1, 2] - * - * See also Array#+. + * a = [0, 1] + * a.concat([2, 3], [4, 5]) # => [0, 1, 2, 3, 4, 5] */ static VALUE @@ -4001,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); @@ -4024,18 +5149,19 @@ rb_ary_concat(VALUE x, VALUE y) /* * call-seq: - * ary * int -> new_ary - * ary * str -> new_string + * array * n -> new_array + * array * string_separator -> new_string * - * Repetition --- With a String argument, equivalent to - * <code>ary.join(str)</code>. + * When non-negative argument \Integer +n+ is given, + * returns a new \Array built by concatenating the +n+ copies of +self+: * - * Otherwise, returns a new array built by concatenating the +int+ 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>: * - * [ 1, 2, 3 ] * 3 #=> [ 1, 2, 3, 1, 2, 3, 1, 2, 3 ] - * [ 1, 2, 3 ] * "," #=> "1,2,3" + * [0, [0, 1], {foo: 0}] * ', ' # => "0, 0, 1, {:foo=>0}" * */ @@ -4048,30 +5174,30 @@ 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_obj_class(ary), 0); - goto out; + ary2 = ary_new(rb_cArray, 0); + 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); - ary2 = ary_new(rb_obj_class(ary), len); + ary2 = ary_new(rb_cArray, len); ARY_SET_LEN(ary2, len); 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; } @@ -4080,29 +5206,22 @@ rb_ary_times(VALUE ary, VALUE times) } } out: - OBJ_INFECT(ary2, ary); - return ary2; } /* * call-seq: - * ary.assoc(obj) -> element_ary or nil + * array.assoc(obj) -> found_array or nil * - * Searches through an array whose elements are also arrays comparing +obj+ - * with the first element of each contained array using <code>obj.==</code>. + * Returns the first element in +self+ that is an \Array + * whose first element <tt>==</tt> +obj+: * - * Returns the first contained array that matches (that is, the first - * associated array), or +nil+ if no match is found. + * a = [{foo: 0}, [2, 4], [4, 5, 6], [4, 5]] + * a.assoc(4) # => [4, 5, 6] * - * See also Array#rassoc + * Returns +nil+ if no such element is found. * - * s1 = [ "colors", "red", "blue", "green" ] - * s2 = [ "letters", "a", "b", "c" ] - * s3 = "foo" - * a = [ s1, s2, s3 ] - * a.assoc("letters") #=> [ "letters", "a", "b", "c" ] - * a.assoc("foo") #=> nil + * Related: #rassoc. */ VALUE @@ -4112,30 +5231,27 @@ 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; } /* * call-seq: - * ary.rassoc(obj) -> element_ary or nil - * - * Searches through the array whose elements are also arrays. + * array.rassoc(obj) -> found_array or nil * - * Compares +obj+ with the second element of each contained array using - * <code>obj.==</code>. + * Returns the first element in +self+ that is an \Array + * whose second element <tt>==</tt> +obj+: * - * Returns the first contained array that matches +obj+. + * a = [{foo: 0}, [2, 4], [4, 5, 6], [4, 5]] + * a.rassoc(4) # => [2, 4] * - * See also Array#assoc. + * Returns +nil+ if no such element is found. * - * a = [ [ 1, "one"], [2, "two"], [3, "three"], ["ii", "two"] ] - * a.rassoc("two") #=> [2, "two"] - * a.rassoc("four") #=> nil + * Related: #assoc. */ VALUE @@ -4145,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; } @@ -4168,38 +5284,42 @@ 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; } /* * call-seq: - * ary == other_ary -> bool + * array == other_array -> true or false + * + * 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>: * - * Equality --- Two arrays are equal if they contain the same number of - * elements and if each element is equal to (according to Object#==) the - * corresponding element in +other_ary+. + * a0 = [:foo, 'bar', 2] + * a1 = [:foo, 'bar', 2.0] + * a1 == a0 # => true + * [] == [] # => true * - * [ "a", "c" ] == [ "a", "c", 7 ] #=> false - * [ "a", "c", 7 ] == [ "a", "c", 7 ] #=> true - * [ "a", "c", 7 ] == [ "a", "d", "f" ] #=> false + * Otherwise, returns +false+. * + * This method is different from method Array#eql?, + * which compares elements using <tt>Object#eql?</tt>. */ static VALUE @@ -4207,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; @@ -4224,18 +5344,27 @@ 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; } /* * call-seq: - * ary.eql?(other) -> true or false + * array.eql? other_array -> true or false + * + * 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+. * - * Returns +true+ if +self+ and +other+ are the same object, - * or are both arrays with the same content (according to Object#eql?). + * This method is different from method Array#==, + * which compares using method <tt>Object#==</tt>. */ static VALUE @@ -4250,14 +5379,15 @@ rb_ary_eql(VALUE ary1, VALUE ary2) /* * call-seq: - * ary.hash -> integer + * array.hash -> integer * - * Compute a hash-code for this array. + * Returns the integer hash value for +self+. * - * Two arrays with the same content will have the same hash code (and will - * compare using #eql?). + * 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 * - * See also Object#hash. */ static VALUE @@ -4270,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); @@ -4279,14 +5409,13 @@ rb_ary_hash(VALUE ary) /* * call-seq: - * ary.include?(object) -> true or false + * array.include?(obj) -> true or false * - * Returns +true+ if the given +object+ is present in +self+ (that is, if any - * element <code>==</code> +object+), otherwise returns +false+. + * Returns +true+ if for some index +i+ in +self+, <tt>obj == self[i]</tt>; + * otherwise +false+: * - * a = [ "a", "b", "c" ] - * a.include?("b") #=> true - * a.include?("z") #=> false + * [0, 1, 2].include?(2) # => true + * [0, 1, 2].include?(3) # => false */ VALUE @@ -4296,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; } @@ -4311,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; } @@ -4327,45 +5456,46 @@ 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; } /* * call-seq: - * ary <=> other_ary -> -1, 0, +1 or nil + * array <=> other_array -> -1, 0, or 1 + * + * Returns -1, 0, or 1 as +self+ is less than, equal to, or greater than +other_array+. + * 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 * - * Comparison --- Returns an integer (+-1+, +0+, or <code>+1</code>) if this - * array is less than, equal to, or greater than +other_ary+. + * When all results are zero: * - * Each object in each array is compared (using the <=> operator). + * - Returns -1 if +array+ is smaller than +other_array+: * - * Arrays are compared in an "element-wise" manner; the first element of +ary+ - * is compared with the first one of +other_ary+ using the <=> operator, then - * each of the second elements, etc... - * As soon as the result of any such comparison is non zero (i.e. the two - * corresponding elements are not equal), that result is returned for the - * whole array comparison. + * [0, 1, 2] <=> [0, 1, 2, 3] # => -1 * - * If all the elements are equal, then the result is based on a comparison of - * the array lengths. Thus, two arrays are "equal" according to Array#<=> if, - * and only if, they have the same length and the value of each element is - * equal to the value of the corresponding element in the other array. + * - Returns 1 if +array+ is larger than +other_array+: * - * +nil+ is returned if the +other_ary+ is not an array or if the comparison - * of two elements returned +nil+. + * [0, 1, 2] <=> [0, 1] # => 1 * - * [ "a", "a", "c" ] <=> [ "a", "b", "c" ] #=> -1 - * [ 1, 2, 3, 4, 5, 6 ] <=> [ 1, 2 ] #=> +1 - * [ 1, 2 ] <=> [ 1, :two ] #=> nil + * - Returns 0 if +array+ and +other_array+ are the same size: + * + * [0, 1, 2] <=> [0, 1, 2] # => 0 * */ @@ -4379,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); @@ -4392,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; } @@ -4421,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; } @@ -4440,31 +5570,28 @@ 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); } } /* * call-seq: - * ary - other_ary -> new_ary - * - * Array Difference - * - * Returns a new array that is a copy of the original array, removing any - * items that also appear in +other_ary+. The order is preserved from the - * original array. + * array - other_array -> new_array * - * It compares elements using their #hash and #eql? methods for efficiency. + * Returns a new \Array containing only those elements from +array+ + * that are not found in \Array +other_array+; + * items are compared using <tt>eql?</tt>; + * the order from +array+ is preserved: * - * [ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ] + * [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] * - * If you need set-like behavior, see the library class Set. - * - * See also Array#difference. + * Related: Array#difference. */ -static VALUE +VALUE rb_ary_diff(VALUE ary1, VALUE ary2) { VALUE ary3; @@ -4472,21 +5599,22 @@ rb_ary_diff(VALUE ary1, VALUE ary2) long i; ary2 = to_ary(ary2); + if (RARRAY_LEN(ary2) == 0) { return ary_make_shared_copy(ary1); } 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; @@ -4494,22 +5622,19 @@ rb_ary_diff(VALUE ary1, VALUE ary2) /* * call-seq: - * ary.difference(other_ary1, other_ary2, ...) -> ary - * - * Array Difference + * array.difference(*other_arrays) -> new_array * - * Returns a new array that is a copy of the receiver, removing any items - * that also appear in any of the arrays given as arguments. - * The order is preserved from the original array. + * 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: * - * It compares elements using their #hash and #eql? methods for efficiency. + * [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] * - * [ 1, 1, 2, 2, 3, 3, 4, 5 ].difference([ 1, 2, 4 ]) #=> [ 3, 3, 5 ] - * [ 1, 'c', :s, 'yep' ].difference([ 1 ], [ 'a', 'c' ]) #=> [ :s, "yep" ] + * Returns a copy of +self+ if no arguments given. * - * If you need set-like behavior, see the library class Set. - * - * See also Array#-. + * Related: Array#-. */ static VALUE @@ -4531,7 +5656,7 @@ rb_ary_difference_multi(int argc, VALUE *argv, VALUE ary) for (i = 0; i < RARRAY_LEN(ary); i++) { int j; VALUE elt = rb_ary_elt(ary, i); - for (j = 0; j < argc; j++){ + for (j = 0; j < argc; j++) { if (is_hash[j]) { if (rb_hash_stlike_lookup(argv[j], RARRAY_AREF(ary, i), NULL)) break; @@ -4551,17 +5676,19 @@ rb_ary_difference_multi(int argc, VALUE *argv, VALUE ary) /* * call-seq: - * ary & other_ary -> new_ary + * array & other_array -> new_array + * + * 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>: * - * Set Intersection --- Returns a new array containing unique elements common to the - * two arrays. The order is preserved from the original array. + * [0, 1, 2, 3] & [1, 2] # => [1, 2] + * [0, 1, 0, 1] & [0, 1] # => [0, 1] * - * It compares elements using their #hash and #eql? methods for efficiency. + * Preserves order from +array+: * - * [ 1, 1, 3, 5 ] & [ 3, 2, 1 ] #=> [ 1, 3 ] - * [ 'a', 'b', 'b', 'z' ] & [ 'a', 'b', 'c' ] #=> [ 'a', 'b' ] + * [0, 1, 2] & [3, 2, 1, 0] # => [0, 1, 2] * - * See also Array#uniq. + * Related: Array#intersection. */ @@ -4577,29 +5704,62 @@ 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); return ary3; } +/* + * call-seq: + * array.intersection(*other_arrays) -> new_array + * + * 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. + * + * Related: Array#&. + */ + +static VALUE +rb_ary_intersection_multi(int argc, VALUE *argv, VALUE ary) +{ + VALUE result = rb_ary_dup(ary); + int i; + + for (i = 0; i < argc; i++) { + result = rb_ary_and(result, argv[i]); + } + + return result; +} + static int ary_hash_orset(st_data_t *key, st_data_t *value, st_data_t arg, int existing) { @@ -4633,17 +5793,17 @@ rb_ary_union_hash(VALUE hash, VALUE ary2) /* * call-seq: - * ary | other_ary -> new_ary + * array | other_array -> new_array * - * Set Union --- Returns a new array by joining +ary+ with +other_ary+, - * excluding any duplicates and preserving the order from the given arrays. + * Returns the union of +array+ and \Array +other_array+; + * duplicates are removed; order is preserved; + * items are compared using <tt>eql?</tt>: * - * It compares elements using their #hash and #eql? methods for efficiency. + * [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] * - * [ "a", "b", "c" ] | [ "c", "d", "a" ] #=> [ "a", "b", "c", "d" ] - * [ "c", "d", "a" ] | [ "a", "b", "c" ] #=> [ "c", "d", "a", "b" ] - * - * See also Array#union. + * Related: Array#union. */ static VALUE @@ -4653,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); @@ -4669,18 +5829,18 @@ rb_ary_or(VALUE ary1, VALUE ary2) /* * call-seq: - * ary.union(other_ary1, other_ary2, ...) -> ary + * array.union(*other_arrays) -> new_array * - * Set Union --- Returns a new array by joining <code>other_ary</code>s with +self+, - * excluding any duplicates and preserving the order from the given arrays. + * 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>: * - * It compares elements using their #hash and #eql? methods for efficiency. + * [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] * - * [ "a", "b", "c" ].union( [ "c", "d", "a" ] ) #=> [ "a", "b", "c", "d" ] - * [ "a" ].union( ["e", "b"], ["a", "c", "b"] ) #=> [ "a", "e", "b", "c" ] - * [ "a" ].union #=> [ "a" ] + * Returns a copy of +self+ if no arguments given. * - * See also Array#|. + * Related: Array#|. */ static VALUE @@ -4691,7 +5851,7 @@ rb_ary_union_multi(int argc, VALUE *argv, VALUE ary) VALUE hash, ary_union; sum = RARRAY_LEN(ary); - for (i = 0; i < argc; i++){ + for (i = 0; i < argc; i++) { argv[i] = to_ary(argv[i]); sum += RARRAY_LEN(argv[i]); } @@ -4715,30 +5875,191 @@ rb_ary_union_multi(int argc, VALUE *argv, VALUE ary) /* * call-seq: - * ary.max -> obj - * ary.max {|a, b| block} -> obj - * ary.max(n) -> array - * ary.max(n) {|a, b| block} -> array + * ary.intersect?(other_ary) -> true or false * - * Returns the object in _ary_ with the maximum value. The - * first form assumes all objects implement <code>Comparable</code>; - * the second uses the block to return <em>a <=> b</em>. + * Returns +true+ if the array and +other_ary+ have at least one element in + * common, otherwise returns +false+: * - * ary = %w(albatross dog horse) - * ary.max #=> "horse" - * ary.max {|a, b| a.length <=> b.length} #=> "albatross" + * a = [ 1, 2, 3 ] + * b = [ 3, 4, 5 ] + * c = [ 5, 6, 7 ] + * a.intersect?(b) #=> true + * a.intersect?(c) #=> false * - * If the +n+ argument is given, maximum +n+ elements are returned - * as an array. + */ + +static VALUE +rb_ary_intersect_p(VALUE ary1, VALUE ary2) +{ + VALUE hash, v, result, shorter, longer; + st_data_t vv; + long i; + + ary2 = to_ary(ary2); + if (RARRAY_LEN(ary1) == 0 || RARRAY_LEN(ary2) == 0) return Qfalse; + + 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)) return Qtrue; + } + return Qfalse; + } + + shorter = ary1; + longer = ary2; + if (RARRAY_LEN(ary1) > RARRAY_LEN(ary2)) { + longer = ary1; + shorter = ary2; + } + + hash = ary_make_hash(shorter); + result = Qfalse; + + for (i=0; i<RARRAY_LEN(longer); i++) { + v = RARRAY_AREF(longer, i); + vv = (st_data_t)v; + if (rb_hash_stlike_lookup(hash, vv, 0)) { + result = Qtrue; + break; + } + } + ary_recycle_hash(hash); + + return result; +} + +static VALUE +ary_max_generic(VALUE ary, long i, VALUE vmax) +{ + RUBY_ASSERT(i > 0 && i < RARRAY_LEN(ary)); + + VALUE v; + for (; i < RARRAY_LEN(ary); ++i) { + v = RARRAY_AREF(ary, i); + + if (rb_cmpint(rb_funcallv(vmax, id_cmp, 1, &v), vmax, v) < 0) { + vmax = v; + } + } + + return vmax; +} + +static VALUE +ary_max_opt_fixnum(VALUE ary, long i, VALUE vmax) +{ + const long n = RARRAY_LEN(ary); + RUBY_ASSERT(i > 0 && i < n); + RUBY_ASSERT(FIXNUM_P(vmax)); + + VALUE v; + for (; i < n; ++i) { + v = RARRAY_AREF(ary, i); + + if (FIXNUM_P(v)) { + if ((long)vmax < (long)v) { + vmax = v; + } + } + else { + return ary_max_generic(ary, i, vmax); + } + } + + return vmax; +} + +static VALUE +ary_max_opt_float(VALUE ary, long i, VALUE vmax) +{ + const long n = RARRAY_LEN(ary); + RUBY_ASSERT(i > 0 && i < n); + RUBY_ASSERT(RB_FLOAT_TYPE_P(vmax)); + + VALUE v; + for (; i < n; ++i) { + v = RARRAY_AREF(ary, i); + + if (RB_FLOAT_TYPE_P(v)) { + if (rb_float_cmp(vmax, v) < 0) { + vmax = v; + } + } + else { + return ary_max_generic(ary, i, vmax); + } + } + + return vmax; +} + +static VALUE +ary_max_opt_string(VALUE ary, long i, VALUE vmax) +{ + const long n = RARRAY_LEN(ary); + RUBY_ASSERT(i > 0 && i < n); + RUBY_ASSERT(STRING_P(vmax)); + + VALUE v; + for (; i < n; ++i) { + v = RARRAY_AREF(ary, i); + + if (STRING_P(v)) { + if (rb_str_cmp(vmax, v) < 0) { + vmax = v; + } + } + else { + return ary_max_generic(ary, i, vmax); + } + } + + return vmax; +} + +/* + * call-seq: + * array.max -> element + * array.max {|a, b| ... } -> element + * array.max(n) -> new_array + * 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+. + * + * When no block is given, each element in +self+ must respond to method <tt><=></tt> + * with an \Integer. + * + * 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] + * + * When a block is given, the block must return an \Integer. + * + * 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"] * - * ary = %w[albatross dog horse] - * ary.max(2) #=> ["horse", "dog"] - * ary.max(2) {|a, b| a.length <=> b.length } #=> ["albatross", "horse"] */ 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; @@ -4746,52 +6067,167 @@ rb_ary_max(int argc, VALUE *argv, VALUE ary) if (rb_check_arity(argc, 0, 1) && !NIL_P(num = argv[0])) return rb_nmin_run(ary, num, 0, 1, 1); + 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 { - for (i = 0; i < RARRAY_LEN(ary); i++) { - v = RARRAY_AREF(ary, i); - if (result == Qundef || OPTIMIZED_CMP(v, result, cmp_opt) > 0) { - result = v; - } - } - } - if (result == Qundef) return Qnil; + else if (n > 0) { + result = RARRAY_AREF(ary, 0); + if (n > 1) { + if (FIXNUM_P(result) && CMP_OPTIMIZABLE(INTEGER)) { + return ary_max_opt_fixnum(ary, 1, result); + } + 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(FLOAT)) { + return ary_max_opt_float(ary, 1, result); + } + else { + return ary_max_generic(ary, 1, result); + } + } + } + if (UNDEF_P(result)) return Qnil; return result; } +static VALUE +ary_min_generic(VALUE ary, long i, VALUE vmin) +{ + RUBY_ASSERT(i > 0 && i < RARRAY_LEN(ary)); + + VALUE v; + for (; i < RARRAY_LEN(ary); ++i) { + v = RARRAY_AREF(ary, i); + + if (rb_cmpint(rb_funcallv(vmin, id_cmp, 1, &v), vmin, v) > 0) { + vmin = v; + } + } + + return vmin; +} + +static VALUE +ary_min_opt_fixnum(VALUE ary, long i, VALUE vmin) +{ + const long n = RARRAY_LEN(ary); + RUBY_ASSERT(i > 0 && i < n); + RUBY_ASSERT(FIXNUM_P(vmin)); + + VALUE a; + for (; i < n; ++i) { + a = RARRAY_AREF(ary, i); + + if (FIXNUM_P(a)) { + if ((long)vmin > (long)a) { + vmin = a; + } + } + else { + return ary_min_generic(ary, i, vmin); + } + } + + return vmin; +} + +static VALUE +ary_min_opt_float(VALUE ary, long i, VALUE vmin) +{ + const long n = RARRAY_LEN(ary); + RUBY_ASSERT(i > 0 && i < n); + RUBY_ASSERT(RB_FLOAT_TYPE_P(vmin)); + + VALUE a; + for (; i < n; ++i) { + a = RARRAY_AREF(ary, i); + + if (RB_FLOAT_TYPE_P(a)) { + if (rb_float_cmp(vmin, a) > 0) { + vmin = a; + } + } + else { + return ary_min_generic(ary, i, vmin); + } + } + + return vmin; +} + +static VALUE +ary_min_opt_string(VALUE ary, long i, VALUE vmin) +{ + const long n = RARRAY_LEN(ary); + RUBY_ASSERT(i > 0 && i < n); + RUBY_ASSERT(STRING_P(vmin)); + + VALUE a; + for (; i < n; ++i) { + a = RARRAY_AREF(ary, i); + + if (STRING_P(a)) { + if (rb_str_cmp(vmin, a) > 0) { + vmin = a; + } + } + else { + return ary_min_generic(ary, i, vmin); + } + } + + return vmin; +} + /* * call-seq: - * ary.min -> obj - * ary.min {| a,b | block } -> obj - * ary.min(n) -> array - * ary.min(n) {| a,b | block } -> array + * array.min -> element + * array.min { |a, b| ... } -> element + * array.min(n) -> new_array + * array.min(n) { |a, b| ... } -> new_array * - * Returns the object in _ary_ with the minimum value. The - * first form assumes all objects implement <code>Comparable</code>; - * the second uses the block to return <em>a <=> b</em>. + * Returns one of the following: * - * ary = %w(albatross dog horse) - * ary.min #=> "albatross" - * ary.min {|a, b| a.length <=> b.length} #=> "dog" + * - The minimum-valued element from +self+. + * - A new \Array of minimum-valued elements selected from +self+. * - * If the +n+ argument is given, minimum +n+ elements are returned - * as an array. + * When no block is given, each element in +self+ must respond to method <tt><=></tt> + * with an \Integer. + * + * 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] + * + * When a block is given, the block must return an Integer. + * + * 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"] * - * ary = %w[albatross dog horse] - * ary.min(2) #=> ["albatross", "dog"] - * ary.min(2) {|a, b| a.length <=> b.length } #=> ["dog", "horse"] */ 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; @@ -4799,26 +6235,68 @@ rb_ary_min(int argc, VALUE *argv, VALUE ary) if (rb_check_arity(argc, 0, 1) && !NIL_P(num = argv[0])) return rb_nmin_run(ary, num, 0, 0, 1); + 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 { - for (i = 0; i < RARRAY_LEN(ary); i++) { - v = RARRAY_AREF(ary, i); - if (result == Qundef || OPTIMIZED_CMP(v, result, cmp_opt) < 0) { - result = v; - } - } - } - if (result == Qundef) return Qnil; + else if (n > 0) { + result = RARRAY_AREF(ary, 0); + if (n > 1) { + if (FIXNUM_P(result) && CMP_OPTIMIZABLE(INTEGER)) { + return ary_min_opt_fixnum(ary, 1, result); + } + 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(FLOAT)) { + return ary_min_opt_float(ary, 1, result); + } + else { + return ary_min_generic(ary, 1, result); + } + } + } + if (UNDEF_P(result)) return Qnil; return result; } +/* + * call-seq: + * array.minmax -> [min_val, max_val] + * array.minmax {|a, b| ... } -> [min_val, max_val] + * + * Returns a new 2-element \Array containing the minimum and maximum values + * from +self+, either per method <tt><=></tt> or per a given block:. + * + * When no block is given, each element in +self+ must respond to method <tt><=></tt> + * 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) +{ + if (rb_block_given_p()) { + return rb_call_super(0, NULL); + } + return rb_assoc_new(rb_ary_min(0, 0, ary), rb_ary_max(0, 0, ary)); +} + static int push_value(st_data_t key, st_data_t val, st_data_t ary) { @@ -4828,31 +6306,33 @@ push_value(st_data_t key, st_data_t val, st_data_t ary) /* * call-seq: - * ary.uniq! -> ary or nil - * ary.uniq! {|item| ...} -> ary or nil + * array.uniq! -> self or nil + * array.uniq! {|element| ... } -> self or nil * - * Removes duplicate elements from +self+. + * Removes duplicate elements from +self+, the first occurrence always being retained; + * returns +self+ if any elements removed, +nil+ otherwise. * - * If a block is given, it will use the return value of the block for - * comparison. + * With no block given, identifies and removes elements using method <tt>eql?</tt> + * to compare. * - * It compares values using their #hash and #eql? methods for efficiency. + * Returns +self+ if any elements removed: * - * +self+ is traversed in order, and the first occurrence is kept. + * a = [0, 0, 1, 1, 2, 2] + * a.uniq! # => [0, 1, 2] * - * Returns +nil+ if no changes are made (that is, no duplicates are found). + * Returns +nil+ if no elements removed. * - * a = [ "a", "a", "b", "b", "c" ] - * a.uniq! # => ["a", "b", "c"] + * With a block given, calls the block for each element; + * identifies (using method <tt>eql?</tt>) and removes + * elements for which the block returns duplicate values. * - * b = [ "a", "b", "c" ] - * b.uniq! # => nil + * Returns +self+ if any elements removed: * - * c = [["student","sam"], ["student","george"], ["teacher","matz"]] - * c.uniq! {|s| s.first} # => [["student", "sam"], ["teacher", "matz"]] + * a = ['a', 'aa', 'aaa', 'b', 'bb', 'bbb'] + * a.uniq! {|element| element.size } # => ['a', 'aa', 'aaa'] * + * Returns +nil+ if no elements removed. */ - static VALUE rb_ary_uniq_bang(VALUE ary) { @@ -4863,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); @@ -4886,22 +6366,24 @@ rb_ary_uniq_bang(VALUE ary) /* * call-seq: - * ary.uniq -> new_ary - * ary.uniq {|item| ...} -> new_ary - * - * Returns a new array by removing duplicate values in +self+. + * array.uniq -> new_array + * array.uniq {|element| ... } -> new_array * - * If a block is given, it will use the return value of the block for comparison. + * Returns a new \Array containing those elements from +self+ that are not duplicates, + * the first occurrence always being retained. * - * It compares values using their #hash and #eql? methods for efficiency. + * With no block given, identifies and omits duplicates using method <tt>eql?</tt> + * to compare: * - * +self+ is traversed in order, and the first occurrence is kept. + * a = [0, 0, 1, 1, 2, 2] + * a.uniq # => [0, 1, 2] * - * a = [ "a", "a", "b", "b", "c" ] - * a.uniq # => ["a", "b", "c"] + * 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: * - * b = [["student","sam"], ["student","george"], ["teacher","matz"]] - * b.uniq {|s| s.first} # => [["student", "sam"], ["teacher", "matz"]] + * a = ['a', 'aa', 'aaa', 'b', 'bb', 'bbb'] + * a.uniq {|element| element.size } # => ["a", "aa", "aaa"] * */ @@ -4910,32 +6392,32 @@ rb_ary_uniq(VALUE ary) { VALUE hash, uniq; - if (RARRAY_LEN(ary) <= 1) - return rb_ary_dup(ary); - if (rb_block_given_p()) { - hash = ary_make_hash_by(ary); - uniq = rb_hash_values(hash); + if (RARRAY_LEN(ary) <= 1) { + hash = 0; + uniq = rb_ary_dup(ary); + } + else if (rb_block_given_p()) { + 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); } - RBASIC_SET_CLASS(uniq, rb_obj_class(ary)); - ary_recycle_hash(hash); return uniq; } /* * call-seq: - * ary.compact! -> ary or nil - * - * Removes +nil+ elements from the array. + * array.compact! -> self or nil * - * Returns +nil+ if no changes were made, otherwise returns the array. + * Removes all +nil+ elements from +self+. * - * [ "a", nil, "b", nil, "c" ].compact! #=> [ "a", "b", "c" ] - * [ "a", "b", "c" ].compact! #=> nil + * Returns +self+ if any elements removed, otherwise +nil+. */ static VALUE @@ -4949,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); @@ -4963,12 +6445,12 @@ rb_ary_compact_bang(VALUE ary) /* * call-seq: - * ary.compact -> new_ary + * array.compact -> new_array * - * Returns a copy of +self+ with all +nil+ elements removed. + * Returns a new \Array containing all non-+nil+ elements from +self+: * - * [ "a", nil, "b", nil, "c", nil ].compact - * #=> [ "a", "b", "c" ] + * a = [nil, 0, nil, 1, nil, 2, nil] + * a.compact # => [0, 1, 2] */ static VALUE @@ -4981,23 +6463,29 @@ rb_ary_compact(VALUE ary) /* * call-seq: - * ary.count -> int - * ary.count(obj) -> int - * ary.count {|item| block} -> int + * array.count -> an_integer + * array.count(obj) -> an_integer + * array.count {|element| ... } -> an_integer + * + * Returns a count of specified elements. + * + * With no argument and no block, returns the count of all elements: * - * Returns the number of elements. + * [0, 1, 2].count # => 3 + * [].count # => 0 * - * If an argument is given, counts the number of elements which equal +obj+ - * using <code>==</code>. + * With argument +obj+, returns the count of elements <tt>==</tt> to +obj+: * - * If a block is given, counts the number of elements for which the block - * returns a true value. + * [0, 1, 2, 0.0].count(0) # => 2 + * [0, 1, 2].count(3) # => 0 * - * ary = [1, 2, 4, 2] - * ary.count #=> 4 - * ary.count(2) #=> 2 - * ary.count {|x| x%2 == 0} #=> 3 + * 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+. */ static VALUE @@ -5006,106 +6494,151 @@ 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); } static VALUE -flatten(VALUE ary, int level, int *modified) +flatten(VALUE ary, int level) { - long i = 0; - VALUE stack, result, tmp, elt; - st_table *memo; + long i; + VALUE stack, result, tmp = 0, elt, vmemo; + st_table *memo = 0; st_data_t id; - stack = ary_new(0, ARY_DEFAULT_SIZE); + for (i = 0; i < RARRAY_LEN(ary); i++) { + elt = RARRAY_AREF(ary, i); + tmp = rb_check_array_type(elt); + if (!NIL_P(tmp)) { + break; + } + } + if (i == RARRAY_LEN(ary)) { + return ary; + } + result = ary_new(0, RARRAY_LEN(ary)); - memo = st_init_numtable(); - st_insert(memo, (st_data_t)ary, (st_data_t)Qtrue); - *modified = 0; + ary_memcpy(result, 0, i, RARRAY_CONST_PTR_TRANSIENT(ary)); + ARY_SET_LEN(result, i); + + stack = ary_new(0, ARY_DEFAULT_SIZE); + rb_ary_push(stack, ary); + 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); + } + + 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) { - rb_raise(rb_eRuntimeError, "flatten reentered"); - } - if (NIL_P(tmp)) { - rb_ary_push(result, elt); - } - else { - *modified = 1; - id = (st_data_t)tmp; - if (st_lookup(memo, id, 0)) { - st_free_table(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; - } - id = (st_data_t)ary; - st_delete(memo, &id, 0); - tmp = rb_ary_pop(stack); - i = NUM2LONG(tmp); - ary = rb_ary_pop(stack); - } - - st_free_table(memo); - - RBASIC_SET_CLASS(result, rb_obj_class(ary)); + 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); + } + + RBASIC_SET_CLASS(result, rb_cArray); return result; } /* * call-seq: - * ary.flatten! -> ary or nil - * ary.flatten!(level) -> ary or nil + * array.flatten! -> self or nil + * array.flatten!(level) -> self or nil + * + * Replaces each nested \Array in +self+ with the elements from that \Array; + * returns +self+ if any changes, +nil+ otherwise. * - * Flattens +self+ in place. + * With non-negative \Integer argument +level+, flattens recursively through +level+ levels: * - * Returns +nil+ if no modifications were made (i.e., the array contains no - * subarrays.) + * a = [ 0, [ 1, [2, 3], 4 ], 5 ] + * a.flatten!(1) # => [0, 1, [2, 3], 4, 5] + * a = [ 0, [ 1, [2, 3], 4 ], 5 ] + * a.flatten!(2) # => [0, 1, 2, 3, 4, 5] + * a = [ 0, [ 1, [2, 3], 4 ], 5 ] + * a.flatten!(3) # => [0, 1, 2, 3, 4, 5] + * [0, 1, 2].flatten!(1) # => nil * - * The optional +level+ argument determines the level of recursion to flatten. + * 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 + * a = [ 0, [ 1, [2, 3], 4 ], 5 ] + * a.flatten!(-1) # => [0, 1, 2, 3, 4, 5] + * a = [ 0, [ 1, [2, 3], 4 ], 5 ] + * a.flatten!(-2) # => [0, 1, 2, 3, 4, 5] + * [0, 1, 2].flatten!(-1) # => nil * - * a = [ 1, 2, [3, [4, 5] ] ] - * a.flatten! #=> [1, 2, 3, 4, 5] - * a.flatten! #=> nil - * a #=> [1, 2, 3, 4, 5] - * a = [ 1, 2, [3, [4, 5] ] ] - * a.flatten!(1) #=> [1, 2, 3, [4, 5]] */ static VALUE @@ -5119,10 +6652,9 @@ rb_ary_flatten_bang(int argc, VALUE *argv, VALUE ary) if (!NIL_P(lv)) level = NUM2INT(lv); if (level == 0) return Qnil; - result = flatten(ary, level, &mod); - if (mod == 0) { - ary_discard(result); - return Qnil; + result = flatten(ary, level); + if (result == ary) { + return Qnil; } if (!(mod = ARY_EMBED_P(result))) rb_obj_freeze(result); rb_ary_replace(ary, result); @@ -5133,30 +6665,41 @@ rb_ary_flatten_bang(int argc, VALUE *argv, VALUE ary) /* * call-seq: - * ary.flatten -> new_ary - * ary.flatten(level) -> new_ary - * - * Returns a new array that is a one-dimensional flattening of +self+ - * (recursively). - * - * That is, for every element that is an array, extract its elements into - * the new array. - * - * The optional +level+ argument determines the level of recursion to - * flatten. + * array.flatten -> new_array + * array.flatten(level) -> new_array + * + * Returns a new \Array that is a recursive flattening of +self+: + * - Each non-Array element is unchanged. + * - 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 ] + * a.flatten(1) # => [0, 1, [2, 3], 4, 5] + * a = [ 0, [ 1, [2, 3], 4 ], 5 ] + * a.flatten(2) # => [0, 1, 2, 3, 4, 5] + * a = [ 0, [ 1, [2, 3], 4 ], 5 ] + * 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] + * a = [ 0, [ 1, [2, 3], 4 ], 5 ] + * a.flatten(-1) # => [0, 1, 2, 3, 4, 5] + * a = [ 0, [ 1, [2, 3], 4 ], 5 ] + * a.flatten(-2) # => [0, 1, 2, 3, 4, 5] + * [0, 1, 2].flatten(-1) # => [0, 1, 2] * - * s = [ 1, 2, 3 ] #=> [1, 2, 3] - * t = [ 4, 5, 6, [7, 8] ] #=> [4, 5, 6, [7, 8]] - * a = [ s, t, 9, 10 ] #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10] - * a.flatten #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - * a = [ 1, 2, [3, [4, 5] ] ] - * a.flatten(1) #=> [1, 2, 3, [4, 5]] */ static VALUE rb_ary_flatten(int argc, VALUE *argv, VALUE ary) { - int mod = 0, level = -1; + int level = -1; VALUE result; if (rb_check_arity(argc, 0, 1) && !NIL_P(argv[0])) { @@ -5164,257 +6707,170 @@ rb_ary_flatten(int argc, VALUE *argv, VALUE ary) if (level == 0) return ary_make_shared_copy(ary); } - result = flatten(ary, level, &mod); - OBJ_INFECT(result, ary); + result = flatten(ary, level); + if (result == ary) { + result = ary_make_shared_copy(ary); + } return result; } -#define OPTHASH_GIVEN_P(opts) \ - (argc > 0 && !NIL_P((opts) = rb_check_hash_type(argv[argc-1])) && (--argc, 1)) -static ID id_random; - #define RAND_UPTO(max) (long)rb_random_ulong_limited((randgen), (max)-1) -/* - * call-seq: - * ary.shuffle! -> ary - * ary.shuffle!(random: rng) -> ary - * - * Shuffles elements in +self+ in place. - * - * a = [ 1, 2, 3 ] #=> [1, 2, 3] - * a.shuffle! #=> [2, 3, 1] - * a #=> [2, 3, 1] - * - * The optional +rng+ argument will be used as the random number generator. - * - * a.shuffle!(random: Random.new(1)) #=> [1, 3, 2] - */ - static VALUE -rb_ary_shuffle_bang(int argc, VALUE *argv, VALUE ary) +rb_ary_shuffle_bang(rb_execution_context_t *ec, VALUE ary, VALUE randgen) { - VALUE opts, randgen = rb_cRandom; long i, len; - if (OPTHASH_GIVEN_P(opts)) { - VALUE rnd; - ID keyword_ids[1]; - - keyword_ids[0] = id_random; - rb_get_kwargs(opts, keyword_ids, 0, 1, &rnd); - if (rnd != Qundef) { - randgen = rnd; - } - } - rb_check_arity(argc, 0, 0); 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; } - -/* - * call-seq: - * ary.shuffle -> new_ary - * ary.shuffle(random: rng) -> new_ary - * - * Returns a new array with elements of +self+ shuffled. - * - * a = [ 1, 2, 3 ] #=> [1, 2, 3] - * a.shuffle #=> [2, 3, 1] - * a #=> [1, 2, 3] - * - * The optional +rng+ argument will be used as the random number generator. - * - * a.shuffle(random: Random.new(1)) #=> [1, 3, 2] - */ - static VALUE -rb_ary_shuffle(int argc, VALUE *argv, VALUE ary) +rb_ary_shuffle(rb_execution_context_t *ec, VALUE ary, VALUE randgen) { ary = rb_ary_dup(ary); - rb_ary_shuffle_bang(argc, argv, ary); + rb_ary_shuffle_bang(ec, ary, randgen); return ary; } - -/* - * call-seq: - * ary.sample -> obj - * ary.sample(random: rng) -> obj - * ary.sample(n) -> new_ary - * ary.sample(n, random: rng) -> new_ary - * - * Choose a random element or +n+ random elements from the array. - * - * The elements are chosen by using random and unique indices into the array - * in order to ensure that an element doesn't repeat itself unless the array - * already contained duplicate elements. - * - * If the array is empty the first form returns +nil+ and the second form - * returns an empty array. - * - * a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] - * a.sample #=> 7 - * a.sample(4) #=> [6, 4, 2, 5] - * - * The optional +rng+ argument will be used as the random number generator. - * - * a.sample(random: Random.new(1)) #=> 6 - * a.sample(4, random: Random.new(1)) #=> [6, 10, 9, 2] - */ - - static VALUE -rb_ary_sample(int argc, VALUE *argv, VALUE ary) +ary_sample(rb_execution_context_t *ec, VALUE ary, VALUE randgen, VALUE nv, VALUE to_array) { - VALUE nv, result; - VALUE opts, randgen = rb_cRandom; + VALUE result; long n, len, i, j, k, idx[10]; long rnds[numberof(idx)]; long memo_threshold; - if (OPTHASH_GIVEN_P(opts)) { - VALUE rnd; - ID keyword_ids[1]; - - keyword_ids[0] = id_random; - rb_get_kwargs(opts, keyword_ids, 0, 1, &rnd); - if (rnd != Qundef) { - randgen = rnd; - } - } len = RARRAY_LEN(ary); - if (rb_check_arity(argc, 0, 1) == 0) { - if (len < 2) - i = 0; - else - i = RAND_UPTO(len); + if (!to_array) { + if (len < 2) + i = 0; + else + i = RAND_UPTO(len); - return rb_ary_elt(ary, i); + return rb_ary_elt(ary, i); } - nv = argv[0]; 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_values(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); @@ -5422,15 +6878,21 @@ rb_ary_sample(int argc, VALUE *argv, VALUE ary) } static VALUE +ary_sample0(rb_execution_context_t *ec, VALUE ary) +{ + return ary_sample(ec, ary, rb_cRandom, Qfalse, Qfalse); +} + +static VALUE 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 (n == Qnil) return DBL2NUM(HUGE_VAL); + if (NIL_P(n)) return DBL2NUM(HUGE_VAL); mul = NUM2LONG(n); if (mul <= 0) return INT2FIX(0); n = LONG2FIX(mul); @@ -5439,24 +6901,37 @@ rb_ary_cycle_size(VALUE self, VALUE args, VALUE eobj) /* * call-seq: - * ary.cycle(n=nil) {|obj| block} -> nil - * ary.cycle(n=nil) -> Enumerator + * array.cycle {|element| ... } -> nil + * array.cycle(count) {|element| ... } -> nil + * array.cycle -> new_enumerator + * array.cycle(count) -> new_enumerator + * + * 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+: * - * Calls the given block for each element +n+ times or forever if +nil+ is - * given. + * output = [] + * [0, 1].cycle(2) {|element| output.push(element) } # => nil + * output # => [0, 1, 0, 1] * - * Does nothing if a non-positive number is given or the array is empty. + * If +count+ is zero or negative, does not call the block: * - * Returns +nil+ if the loop has finished without getting interrupted. + * [0, 1].cycle(0) {|element| fail 'Cannot happen' } # => nil + * [0, 1].cycle(-1) {|element| fail 'Cannot happen' } # => nil * - * If no block is given, an Enumerator is returned instead. + * When a block is given, and argument is omitted or +nil+, cycles forever: * - * a = ["a", "b", "c"] - * a.cycle {|x| puts x} # print, a, b, c, a, b, c,.. forever. - * a.cycle(2) {|x| puts x} # print, a, b, c, a, b, c. + * # Prints 0 and 1 forever. + * [0, 1].cycle {|element| puts element } + * [0, 1].cycle(nil) {|element| puts element } + * + * When no block is given, returns a new \Enumerator: + * + * [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) { @@ -5481,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. @@ -5495,7 +6967,7 @@ yield_indexed_values(const VALUE values, const long r, const long *const p) const VALUE result = rb_ary_new2(r); long i; - for (i = 0; i < r; i++) RARRAY_ASET(result, i, RARRAY_AREF(values, p[i])); + for (i = 0; i < r; i++) ARY_SET(result, i, RARRAY_AREF(values, p[i])); ARY_SET_LEN(result, r); rb_yield(result); return !RBASIC(values)->klass; @@ -5519,52 +6991,52 @@ 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; + } } } /* * Returns the product of from, from-1, ..., from - how_many + 1. - * http://en.wikipedia.org/wiki/Pochhammer_symbol + * https://en.wikipedia.org/wiki/Pochhammer_symbol */ static VALUE 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; } @@ -5575,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; } @@ -5602,30 +7074,82 @@ rb_ary_permutation_size(VALUE ary, VALUE args, VALUE eobj) /* * call-seq: - * ary.permutation {|p| block} -> ary - * ary.permutation -> Enumerator - * ary.permutation(n) {|p| block} -> ary - * ary.permutation(n) -> Enumerator + * array.permutation {|element| ... } -> self + * array.permutation(n) {|element| ... } -> self + * array.permutation -> new_enumerator + * array.permutation(n) -> new_enumerator + * + * When invoked with a block, yield all permutations of elements of +self+; returns +self+. + * The order of permutations is indeterminate. + * + * When a block and an in-range positive \Integer argument +n+ (<tt>0 < n <= self.size</tt>) + * 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] + * [1, 2, 0] + * [2, 0, 1] + * [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: * - * When invoked with a block, yield all permutations of length +n+ of the - * elements of the array, then return the array itself. + * a = [0, 1, 2] + * a.permutation(-1) {|permutation| fail 'Cannot happen' } + * a.permutation(4) {|permutation| fail 'Cannot happen' } * - * If +n+ is not specified, yield all permutations of all elements. + * When a block given but no argument, + * behaves the same as <tt>a.permutation(a.size)</tt>: * - * The implementation makes no guarantees about the order in which the - * permutations are yielded. + * a = [0, 1, 2] + * a.permutation {|permutation| p permutation } * - * If no block is given, an Enumerator is returned instead. + * Output: * - * Examples: + * [0, 1, 2] + * [0, 2, 1] + * [1, 0, 2] + * [1, 2, 0] + * [2, 0, 1] + * [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)> * - * a = [1, 2, 3] - * a.permutation.to_a #=> [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] - * a.permutation(1).to_a #=> [[1],[2],[3]] - * a.permutation(2).to_a #=> [[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]] - * a.permutation(3).to_a #=> [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] - * a.permutation(0).to_a #=> [[]] # one permutation of length 0 - * a.permutation(4).to_a #=> [] # no permutations of length 4 */ static VALUE @@ -5640,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; } @@ -5674,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); } } @@ -5698,26 +7222,55 @@ rb_ary_combination_size(VALUE ary, VALUE args, VALUE eobj) /* * call-seq: - * ary.combination(n) {|c| block} -> ary - * ary.combination(n) -> Enumerator + * array.combination(n) {|element| ... } -> self + * array.combination(n) -> new_enumerator + * + * Calls the block, if given, with combinations of elements of +self+; + * returns +self+. The order of combinations is indeterminate. + * + * When a block and an in-range positive \Integer argument +n+ (<tt>0 < n <= self.size</tt>) + * 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] * - * When invoked with a block, yields all combinations of length +n+ of elements - * from the array and then returns the array itself. + * Another example: * - * The implementation makes no guarantees about the order in which the - * combinations are yielded. + * a = [0, 1, 2] + * a.combination(3) {|combination| p combination } * - * If no block is given, an Enumerator is returned instead. + * Output: * - * Examples: + * [0, 1, 2] * - * a = [1, 2, 3, 4] - * a.combination(1).to_a #=> [[1],[2],[3],[4]] - * a.combination(2).to_a #=> [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]] - * a.combination(3).to_a #=> [[1,2,3],[1,2,4],[1,3,4],[2,3,4]] - * a.combination(4).to_a #=> [[1,2,3,4]] - * a.combination(0).to_a #=> [[]] # one combination of length 0 - * a.combination(5).to_a #=> [] # no combinations of length 5 + * 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)> * */ @@ -5730,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; } @@ -5772,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); } } @@ -5795,37 +7348,79 @@ 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); } /* * call-seq: - * ary.repeated_permutation(n) {|p| block} -> ary - * ary.repeated_permutation(n) -> Enumerator + * array.repeated_permutation(n) {|permutation| ... } -> self + * array.repeated_permutation(n) -> new_enumerator + * + * Calls the block with each repeated permutation of length +n+ of the elements of +self+; + * each permutation is an \Array; + * returns +self+. The order of the permutations is indeterminate. + * + * When a block and a positive \Integer argument +n+ are given, calls the block with each + * +n+-tuple repeated permutation of the elements of +self+. + * 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] + * [1, 0] + * [1, 1] + * [1, 2] + * [2, 0] + * [2, 1] + * [2, 2] * - * When invoked with a block, yield all repeated permutations of length +n+ of - * the elements of the array, then return the array itself. + * If +n+ is zero, calls the block once with an empty \Array. * - * The implementation makes no guarantees about the order in which the repeated - * permutations are yielded. + * If +n+ is negative, does not call the block: * - * If no block is given, an Enumerator is returned instead. + * a.repeated_permutation(-1) {|permutation| fail 'Cannot happen' } * - * Examples: + * 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 # => [[]] + * e = a.repeated_permutation(1) + * e.size # => 3 + * e.to_a # => [[0], [1], [2]] + * 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]] * - * a = [1, 2] - * a.repeated_permutation(1).to_a #=> [[1], [2]] - * a.repeated_permutation(2).to_a #=> [[1,1],[1,2],[2,1],[2,2]] - * a.repeated_permutation(3).to_a #=> [[1,1,1],[1,1,2],[1,2,1],[1,2,2], - * # [2,1,1],[2,1,2],[2,2,1],[2,2,2]] - * a.repeated_permutation(0).to_a #=> [[]] # one permutation of length 0 */ - static VALUE rb_ary_repeated_permutation(VALUE ary, VALUE num) { @@ -5836,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; } @@ -5866,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); } } @@ -5888,35 +7483,71 @@ 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); } /* * call-seq: - * ary.repeated_combination(n) {|c| block} -> ary - * ary.repeated_combination(n) -> Enumerator + * array.repeated_combination(n) {|combination| ... } -> self + * array.repeated_combination(n) -> new_enumerator + * + * Calls the block with each repeated combination of length +n+ of the elements of +self+; + * each combination is an \Array; + * returns +self+. The order of the combinations is indeterminate. + * + * When a block and a positive \Integer argument +n+ are given, calls the block with each + * +n+-tuple repeated combination of the elements of +self+. + * The number of combinations is <tt>(n+1)(n+2)/2</tt>. + * + * +n+ = 1: * - * When invoked with a block, yields all repeated combinations of length +n+ of - * elements from the array and then returns the array itself. + * a = [0, 1, 2] + * a.repeated_combination(1) {|combination| p combination } * - * The implementation makes no guarantees about the order in which the repeated - * combinations are yielded. + * Output: * - * If no block is given, an Enumerator is returned instead. + * [0] + * [1] + * [2] * - * Examples: + * +n+ = 2: * - * a = [1, 2, 3] - * a.repeated_combination(1).to_a #=> [[1], [2], [3]] - * a.repeated_combination(2).to_a #=> [[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]] - * a.repeated_combination(3).to_a #=> [[1,1,1],[1,1,2],[1,1,3],[1,2,2],[1,2,3], - * # [1,3,3],[2,2,2],[2,2,3],[2,3,3],[3,3,3]] - * a.repeated_combination(4).to_a #=> [[1,1,1,1],[1,1,1,2],[1,1,1,3],[1,1,2,2],[1,1,2,3], - * # [1,1,3,3],[1,2,2,2],[1,2,2,3],[1,2,3,3],[1,3,3,3], - * # [2,2,2,2],[2,2,2,3],[2,2,3,3],[2,3,3,3],[3,3,3,3]] - * a.repeated_combination(0).to_a #=> [[]] # one combination of length 0 + * a.repeated_combination(2) {|combination| p combination } + * + * Output: + * + * [0, 0] + * [0, 1] + * [0, 2] + * [1, 1] + * [1, 2] + * [2, 2] + * + * 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 # => [[]] + * e = a.repeated_combination(1) + * e.size # => 3 + * e.to_a # => [[0], [1], [2]] + * e = a.repeated_combination(2) + * e.size # => 6 + * e.to_a # => [[0, 0], [0, 1], [0, 2], [1, 1], [1, 2], [2, 2]] * */ @@ -5929,58 +7560,97 @@ 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; } /* * call-seq: - * ary.product(other_ary, ...) -> new_ary - * ary.product(other_ary, ...) {|p| block} -> ary + * array.product(*other_arrays) -> new_array + * 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+: + * + * - 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] + * p = a.product(a1) + * p.size # => 6 # a.size * a1.size + * p # => [[0, 3], [0, 4], [1, 3], [1, 4], [2, 3], [2, 4]] + * p = a.product(a1, a2) + * p.size # => 12 # a.size * a1.size * a2.size + * p # => [[0, 3, 5], [0, 3, 6], [0, 4, 5], [0, 4, 6], [1, 3, 5], [1, 3, 6], [1, 4, 5], [1, 4, 6], [2, 3, 5], [2, 3, 6], [2, 4, 5], [2, 4, 6]] + * + * If any argument is an empty \Array, returns an empty \Array. + * + * 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] + * [1, 4] + * [2, 3] + * [2, 4] * - * Returns an array of all combinations of elements from all arrays. + * If any argument is an empty \Array, does not call the block: * - * The length of the returned array is the product of the length of +self+ and - * the argument arrays. + * a.product(a1, a2, []) {|combination| fail 'Cannot happen' } * - * If given a block, #product will yield all combinations and return +self+ - * instead. + * If no argument is given, yields each element of +self+ as a 1-element \Array: + * + * a.product {|combination| p combination } + * + * Output: + * + * [0] + * [1] + * [2] * - * [1,2,3].product([4,5]) #=> [[1,4],[1,5],[2,4],[2,5],[3,4],[3,5]] - * [1,2].product([1,2]) #=> [[1,1],[1,2],[2,1],[2,2]] - * [1,2].product([3,4],[5,6]) #=> [[1,3,5],[1,3,6],[1,4,5],[1,4,6], - * # [2,3,5],[2,3,6],[2,4,5],[2,4,6]] - * [1,2].product() #=> [[1],[2]] - * [1,2].product([]) #=> [] */ 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 */ @@ -6001,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; @@ -6066,16 +7736,19 @@ done: /* * call-seq: - * ary.take(n) -> new_ary - * - * Returns first +n+ elements from the array. + * array.take(n) -> new_array * - * If a negative number is given, raises an ArgumentError. + * Returns a new \Array containing the first +n+ element of +self+, + * where +n+ is a non-negative \Integer; + * does not modify +self+. * - * See also Array#drop + * Examples: * - * a = [1, 2, 3, 4, 5, 0] - * a.take(3) #=> [1, 2, 3] + * 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] * */ @@ -6084,25 +7757,31 @@ 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); } /* * call-seq: - * ary.take_while {|obj| block} -> new_ary - * ary.take_while -> Enumerator + * array.take_while {|element| ... } -> new_array + * array.take_while -> new_enumerator * - * Passes elements to the block until the block returns +nil+ or +false+, then - * stops iterating and returns an array of all prior elements. + * Returns a new \Array containing zero or more leading elements of +self+; + * does not modify +self+. * - * If no block is given, an Enumerator is returned instead. + * 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: * - * See also Array#drop_while + * 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] * - * a = [1, 2, 3, 4, 5, 0] - * a.take_while {|i| i < 3} #=> [1, 2] + * With no block given, returns a new \Enumerator: + * + * [0, 1].take_while # => #<Enumerator: [0, 1]:take_while> * */ @@ -6113,24 +7792,25 @@ 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)); } /* * call-seq: - * ary.drop(n) -> new_ary - * - * Drops first +n+ elements from +ary+ and returns the rest of the elements in - * an array. + * array.drop(n) -> new_array * - * If a negative number is given, raises an ArgumentError. + * Returns a new \Array containing all but the first +n+ element of +self+, + * where +n+ is a non-negative \Integer; + * does not modify +self+. * - * See also Array#take + * Examples: * - * a = [1, 2, 3, 4, 5, 0] - * a.drop(3) #=> [4, 5, 0] + * 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] * */ @@ -6140,29 +7820,32 @@ 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)); - if (result == Qnil) result = rb_ary_new(); + if (NIL_P(result)) result = rb_ary_new(); return result; } /* * call-seq: - * ary.drop_while {|obj| block} -> new_ary - * ary.drop_while -> Enumerator + * array.drop_while {|element| ... } -> new_array + * array.drop_while -> new_enumerator + + * Returns a new \Array containing zero or more trailing elements of +self+; + * does not modify +self+. * - * Drops elements up to, but not including, the first element for which the - * block returns +nil+ or +false+ and returns an array containing the - * remaining elements. + * 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: * - * If no block is given, an Enumerator is returned instead. + * a = [0, 1, 2, 3, 4, 5] + * a.drop_while {|element| element < 3 } # => [3, 4, 5] * - * See also Array#take_while + * With no block given, returns a new \Enumerator: * - * a = [1, 2, 3, 4, 5, 0] - * a.drop_while {|i| i < 3 } #=> [3, 4, 5, 0] + * [0, 1].drop_while # => # => #<Enumerator: [0, 1]:drop_while> * */ @@ -6173,17 +7856,42 @@ 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)); } /* * call-seq: - * ary.any? [{|obj| block} ] -> true or false - * ary.any?(pattern) -> true or false + * array.any? -> true or false + * array.any? {|element| ... } -> true or false + * array.any?(obj) -> true or false + * + * Returns +true+ if any element of +self+ meets a given criterion. + * + * 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 * - * See also Enumerable#any? + * 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 + * [0, 1, 2].any?(1) # => true + * [0, 1, 2].any?(3) # => false + * + * Related: Enumerable#any? */ static VALUE @@ -6197,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) { @@ -6207,19 +7915,43 @@ 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; } /* * call-seq: - * ary.all? [{|obj| block} ] -> true or false - * ary.all?(pattern) -> true or false + * array.all? -> true or false + * array.all? {|element| ... } -> true or false + * array.all?(obj) -> true or false + * + * Returns +true+ if all elements of +self+ meet a given criterion. + * + * 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: * - * See also Enumerable#all? + * [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 + * [0, 0, 0].all?(0) # => true + * [0, 1, 2].all?(1) # => false + * + * Related: Enumerable#all? */ static VALUE @@ -6252,10 +7984,34 @@ rb_ary_all_p(int argc, VALUE *argv, VALUE ary) /* * call-seq: - * ary.none? [{|obj| block} ] -> true or false - * ary.none?(pattern) -> true or false + * array.none? -> true or false + * array.none? {|element| ... } -> true or false + * array.none?(obj) -> true or false + * + * Returns +true+ if no element of +self+ meet a given criterion. + * + * With no block given and no argument, returns +true+ if +self+ has no truthy elements, + * +false+ otherwise: * - * See also Enumerable#none? + * [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 + * [0, 1, 2].none?(3) # => true + * [0, 1, 2].none?(1) # => false + * + * Related: Enumerable#none? */ static VALUE @@ -6288,10 +8044,38 @@ rb_ary_none_p(int argc, VALUE *argv, VALUE ary) /* * call-seq: - * ary.one? [{|obj| block} ] -> true or false - * ary.one?(pattern) -> true or false + * array.one? -> true or false + * array.one? {|element| ... } -> true or false + * array.one?(obj) -> true or false + * + * Returns +true+ if exactly one element of +self+ meets a given criterion. + * + * 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 + * [].one? # => false + * + * 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: * - * See also Enumerable#one? + * [0, 1, 2].one?(0) # => true + * [0, 0, 1].one?(0) # => false + * [1, 1, 2].one?(0) # => false + * ['food', 'drink'].one?(/bar/) # => false + * ['food', 'drink'].one?(/foo/) # => true + * [].one?(/foo/) # => false + * + * Related: Enumerable#one? */ static VALUE @@ -6333,19 +8117,22 @@ rb_ary_one_p(int argc, VALUE *argv, VALUE ary) } /* - * call-seq: - * ary.dig(idx, ...) -> object + * call-seq: + * array.dig(index, *identifiers) -> object * - * Extracts the nested value specified by the sequence of <i>idx</i> - * objects by calling +dig+ at each step, returning +nil+ if any - * intermediate step is +nil+. + * Finds and returns the object in nested objects + * that is specified by +index+ and +identifiers+. + * The nested objects may be instances of various classes. + * See {Dig Methods}[rdoc-ref:dig_methods.rdoc]. * - * a = [[1, [2, 3]]] + * 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 * - * a.dig(0, 1, 1) #=> 3 - * a.dig(1, 2, 3) #=> nil - * a.dig(0, 0, 0) #=> TypeError: Integer does not have #dig method - * [42, {foo: :bar}].dig(1, :foo) #=> :bar */ static VALUE @@ -6363,14 +8150,8 @@ finish_exact_sum(long n, VALUE r, VALUE v, int z) { if (n != 0) v = rb_fix_plus(LONG2FIX(n), v); - if (r != Qundef) { - /* r can be an Integer when mathn is loaded */ - if (FIXNUM_P(r)) - v = rb_fix_plus(r, v); - else if (RB_TYPE_P(r, T_BIGNUM)) - v = rb_big_plus(r, v); - else - v = rb_rational_plus(r, v); + if (!UNDEF_P(r)) { + v = rb_rational_plus(r, v); } else if (!n && z) { v = rb_fix_plus(LONG2FIX(0), v); @@ -6380,43 +8161,41 @@ finish_exact_sum(long n, VALUE r, VALUE v, int z) /* * call-seq: - * ary.sum(init=0) -> number - * ary.sum(init=0) {|e| expr } -> number - * - * Returns the sum of elements. - * For example, [e1, e2, e3].sum returns init + e1 + e2 + e3. + * array.sum(init = 0) -> object + * array.sum(init = 0) {|element| ... } -> object * - * If a block is given, the block is applied to each element - * before addition. + * When no block is given, returns the object equivalent to: * - * If <i>ary</i> is empty, it returns <i>init</i>. + * sum = init + * array.each {|element| sum += element } + * sum * - * [].sum #=> 0 - * [].sum(0.0) #=> 0.0 - * [1, 2, 3].sum #=> 6 - * [3, 5.5].sum #=> 8.5 - * [2.5, 3.0].sum(0.0) {|e| e * e } #=> 15.25 - * [Object.new].sum #=> TypeError + * For example, <tt>[e1, e2, e3].sum</tt> returns <tt>init + e1 + e2 + e3</tt>. * - * The (arithmetic) mean value of an array can be obtained as follows. + * Examples: * - * mean = ary.sum(0.0) / ary.length + * a = [0, 1, 2, 3] + * a.sum # => 6 + * a.sum(100) # => 106 * - * This method can be used for non-numeric objects by - * explicit <i>init</i> argument. + * The elements need not be numeric, but must be <tt>+</tt>-compatible + * with each other and with +init+: * - * ["a", "b", "c"].sum("") #=> "abc" - * [[1], [[2]], [3]].sum([]) #=> [1, [2], 3] + * a = ['abc', 'def', 'ghi'] + * a.sum('jkl') # => "jklabcdefghi" * - * However, Array#join and Array#flatten is faster than Array#sum for - * array of strings and array of arrays. + * 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", "b", "c"].join #=> "abc" - * [[1], [[2]], [3]].flatten(1) #=> [1, [2], 3] + * a = ['zero', 1, :two] + * s = a.sum('Coerced and concatenated: ') {|element| element.to_s } + * s # => "Coerced and concatenated: zero1two" * + * Notes: * - * Array#sum method may not respect method redefinition of "+" methods - * such as Integer#+. + * - 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#+. * */ @@ -6447,10 +8226,10 @@ rb_ary_sum(int argc, VALUE *argv, VALUE ary) n = 0; } } - else if (RB_TYPE_P(e, T_BIGNUM)) + 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); @@ -6467,15 +8246,15 @@ rb_ary_sum(int argc, VALUE *argv, VALUE ary) if (RB_FLOAT_TYPE_P(e)) { /* * Kahan-Babuska balancing compensated summation algorithm - * See http://link.springer.com/article/10.1007/s00607-005-0139-x + * See https://link.springer.com/article/10.1007/s00607-005-0139-x */ double f, c; + double x, t; f = NUM2DBL(v); c = 0.0; goto has_float_value; for (; i < RARRAY_LEN(ary); i++) { - double x, t; e = RARRAY_AREF(ary, i); if (block_given) e = rb_yield(e); @@ -6484,7 +8263,7 @@ rb_ary_sum(int argc, VALUE *argv, VALUE ary) x = RFLOAT_VALUE(e); else if (FIXNUM_P(e)) x = FIX2LONG(e); - else if (RB_TYPE_P(e, T_BIGNUM)) + else if (RB_BIGNUM_TYPE_P(e)) x = rb_big2dbl(e); else if (RB_TYPE_P(e, T_RATIONAL)) x = rb_num2dbl(e); @@ -6530,56 +8309,135 @@ rb_ary_sum(int argc, VALUE *argv, VALUE ary) return v; } +static VALUE +rb_ary_deconstruct(VALUE ary) +{ + return ary; +} + /* - * Arrays are ordered, integer-indexed collections of any object. + * 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 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 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. + * + * 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]. * - * Array indexing starts at 0, as in C or Java. A negative index is assumed - * to be relative to the end of the array---that is, an index of -1 indicates - * the last element of the array, -2 is the next to last element in the - * array, and so on. * * == Creating Arrays * - * A new array can be created by using the literal constructor - * <code>[]</code>. Arrays can contain different types of objects. For - * example, the array below contains an Integer, a String and a Float: + * You can create an \Array object explicitly with: * - * ary = [1, "two", 3.0] #=> [1, "two", 3.0] + * - An {array literal}[rdoc-ref:literals.rdoc@Array+Literals]: * - * An array can also be created by explicitly calling Array.new with zero, one - * (the initial size of the Array) or two arguments (the initial size and a - * default object). + * [1, 'one', :one, [2, 'two', :two]] * - * ary = Array.new #=> [] - * Array.new(3) #=> [nil, nil, nil] - * Array.new(3, true) #=> [true, true, true] + * - A {%w or %W: string-array Literal}[rdoc-ref:literals.rdoc@25w+and+-25W-3A+String-Array+Literals]: * - * 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. + * %w[foo bar baz] # => ["foo", "bar", "baz"] + * %w[1 % *] # => ["1", "%", "*"] * - * 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: + * - A {%i pr %I: symbol-array Literal}[rdoc-ref:literals.rdoc@25i+and+-25I-3A+Symbol-Array+Literals]: * - * Array.new(4) {Hash.new} #=> [{}, {}, {}, {}] - * Array.new(4) {|i| i.to_s } #=> ["0", "1", "2", "3"] + * %i[foo bar baz] # => [:foo, :bar, :baz] + * %i[1 % *] # => [:"1", :%, :*] * - * This is also a quick way to build up multi-dimensional arrays: + * - \Method Kernel#Array: * - * empty_table = Array.new(3) {Array.new(3)} - * #=> [[nil, nil, nil], [nil, nil, nil], [nil, nil, nil]] + * 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"]] * - * 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. + * - \Method Array.new: * - * Array({:a => "a", :b => "b"}) #=> [[:a, "a"], [:b, "b"]] + * 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. @@ -6627,7 +8485,7 @@ rb_ary_sum(int argc, VALUE *argv, 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. @@ -6665,7 +8523,7 @@ rb_ary_sum(int argc, VALUE *argv, 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: * @@ -6707,9 +8565,9 @@ rb_ary_sum(int argc, VALUE *argv, 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. @@ -6735,7 +8593,8 @@ rb_ary_sum(int argc, VALUE *argv, 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 @@ -6766,18 +8625,198 @@ rb_ary_sum(int argc, VALUE *argv, VALUE ary) * arr.keep_if {|a| a < 4} #=> [1, 2, 3] * arr #=> [1, 2, 3] * + * == What's Here + * + * First, what's elsewhere. \Class \Array: + * + * - 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}[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 + * + * - ::[]: 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. + * + * === Methods for Comparing + * + * - #<=>: 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. + * - #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. + * + * === 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 +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 + * + * - #&: 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. + * + * === 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. + * + * === 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. */ void Init_Array(void) { -#undef rb_intern -#define rb_intern(str) rb_intern_const(str) - rb_cArray = rb_define_class("Array", rb_cObject); 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); @@ -6802,6 +8841,8 @@ Init_Array(void) rb_define_method(rb_cArray, "concat", rb_ary_concat_multi, -1); rb_define_method(rb_cArray, "union", rb_ary_union_multi, -1); rb_define_method(rb_cArray, "difference", rb_ary_difference_multi, -1); + rb_define_method(rb_cArray, "intersection", rb_ary_intersection_multi, -1); + rb_define_method(rb_cArray, "intersect?", rb_ary_intersect_p, 1); rb_define_method(rb_cArray, "<<", rb_ary_push, 1); rb_define_method(rb_cArray, "push", rb_ary_push_m, -1); rb_define_alias(rb_cArray, "append", "push"); @@ -6814,7 +8855,7 @@ Init_Array(void) rb_define_method(rb_cArray, "each_index", rb_ary_each_index, 0); rb_define_method(rb_cArray, "reverse_each", rb_ary_reverse_each, 0); rb_define_method(rb_cArray, "length", rb_ary_length, 0); - rb_define_alias(rb_cArray, "size", "length"); + rb_define_method(rb_cArray, "size", rb_ary_length, 0); rb_define_method(rb_cArray, "empty?", rb_ary_empty_p, 0); rb_define_method(rb_cArray, "find_index", rb_ary_index, -1); rb_define_method(rb_cArray, "index", rb_ary_index, -1); @@ -6865,6 +8906,7 @@ Init_Array(void) rb_define_method(rb_cArray, "max", rb_ary_max, -1); rb_define_method(rb_cArray, "min", rb_ary_min, -1); + rb_define_method(rb_cArray, "minmax", rb_ary_minmax, 0); rb_define_method(rb_cArray, "uniq", rb_ary_uniq, 0); rb_define_method(rb_cArray, "uniq!", rb_ary_uniq_bang, 0); @@ -6873,9 +8915,6 @@ Init_Array(void) rb_define_method(rb_cArray, "flatten", rb_ary_flatten, -1); rb_define_method(rb_cArray, "flatten!", rb_ary_flatten_bang, -1); rb_define_method(rb_cArray, "count", rb_ary_count, -1); - rb_define_method(rb_cArray, "shuffle!", rb_ary_shuffle_bang, -1); - rb_define_method(rb_cArray, "shuffle", rb_ary_shuffle, -1); - rb_define_method(rb_cArray, "sample", rb_ary_sample, -1); rb_define_method(rb_cArray, "cycle", rb_ary_cycle, -1); rb_define_method(rb_cArray, "permutation", rb_ary_permutation, -1); rb_define_method(rb_cArray, "combination", rb_ary_combination, 1); @@ -6896,5 +8935,7 @@ Init_Array(void) rb_define_method(rb_cArray, "dig", rb_ary_dig, -1); rb_define_method(rb_cArray, "sum", rb_ary_sum, -1); - id_random = rb_intern("random"); + rb_define_method(rb_cArray, "deconstruct", rb_ary_deconstruct, 0); } + +#include "array.rbinc" diff --git a/array.rb b/array.rb new file mode 100644 index 0000000000..b9fa9844e6 --- /dev/null +++ b/array.rb @@ -0,0 +1,69 @@ +class Array + # call-seq: + # array.shuffle!(random: Random) -> array + # + # Shuffles the elements of +self+ in place. + # a = [1, 2, 3] #=> [1, 2, 3] + # a.shuffle! #=> [2, 3, 1] + # a #=> [2, 3, 1] + # + # The optional +random+ argument will be used as the random number generator: + # a.shuffle!(random: Random.new(1)) #=> [1, 3, 2] + def shuffle!(random: Random) + Primitive.rb_ary_shuffle_bang(random) + end + + # call-seq: + # array.shuffle(random: Random) -> new_ary + # + # Returns a new array with elements of +self+ shuffled. + # a = [1, 2, 3] #=> [1, 2, 3] + # a.shuffle #=> [2, 3, 1] + # a #=> [1, 2, 3] + # + # The optional +random+ argument will be used as the random number generator: + # a.shuffle(random: Random.new(1)) #=> [1, 3, 2] + def shuffle(random: Random) + Primitive.rb_ary_shuffle(random) + end + + # call-seq: + # array.sample(random: Random) -> object + # array.sample(n, random: Random) -> new_ary + # + # Returns random elements from +self+. + # + # When no arguments are given, returns a random element from +self+: + # a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + # a.sample # => 3 + # a.sample # => 8 + # If +self+ is empty, returns +nil+. + # + # When argument +n+ is given, returns a new \Array containing +n+ random + # elements from +self+: + # a.sample(3) # => [8, 9, 2] + # a.sample(6) # => [9, 6, 10, 3, 1, 4] + # Returns no more than <tt>a.size</tt> elements + # (because no new duplicates are introduced): + # a.sample(a.size * 2) # => [6, 4, 1, 8, 5, 9, 10, 2, 3, 7] + # But +self+ may contain duplicates: + # a = [1, 1, 1, 2, 2, 3] + # a.sample(a.size * 2) # => [1, 1, 3, 2, 1, 2] + # The argument +n+ must be a non-negative numeric value. + # The order of the result array is unrelated to the order of +self+. + # Returns a new empty \Array if +self+ is empty. + # + # The optional +random+ argument will be used as the random number generator: + # a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + # a.sample(random: Random.new(1)) #=> 6 + # a.sample(4, random: Random.new(1)) #=> [6, 10, 9, 2] + def sample(n = (ary = false), random: Random) + if Primitive.mandatory_only? + # Primitive.cexpr! %{ rb_ary_sample(self, rb_cRandom, Qfalse, Qfalse) } + Primitive.ary_sample0 + else + # Primitive.cexpr! %{ rb_ary_sample(self, random, n, ary) } + Primitive.ary_sample(random, n, ary) + end + end +end @@ -1,18 +1,23 @@ /* indent-tabs-mode: nil */ +#include "internal.h" +#include "internal/parse.h" +#include "internal/symbol.h" +#include "internal/warnings.h" +#include "iseq.h" +#include "node.h" #include "ruby.h" #include "ruby/encoding.h" #include "ruby/util.h" -#include "internal.h" -#include "node.h" #include "vm_core.h" -#include "iseq.h" + +#include "builtin.h" static VALUE rb_mAST; static VALUE rb_cNode; struct ASTNodeData { rb_ast_t *ast; - NODE *node; + const NODE *node; }; static void @@ -22,9 +27,16 @@ node_gc_mark(void *ptr) rb_gc_mark((VALUE)data->ast); } +static size_t +node_memsize(const void *ptr) +{ + struct ASTNodeData *data = (struct ASTNodeData *)ptr; + return rb_ast_memsize(data->ast); +} + static const rb_data_type_t rb_node_type = { "AST/node", - {node_gc_mark, RUBY_TYPED_DEFAULT_FREE, 0,}, + {node_gc_mark, RUBY_TYPED_DEFAULT_FREE, node_memsize,}, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, }; @@ -32,7 +44,7 @@ static const rb_data_type_t rb_node_type = { static VALUE rb_ast_node_alloc(VALUE klass); static void -setup_node(VALUE obj, rb_ast_t *ast, NODE *node) +setup_node(VALUE obj, rb_ast_t *ast, const NODE *node) { struct ASTNodeData *data; @@ -42,7 +54,7 @@ setup_node(VALUE obj, rb_ast_t *ast, NODE *node) } static VALUE -ast_new_internal(rb_ast_t *ast, NODE *node) +ast_new_internal(rb_ast_t *ast, const NODE *node) { VALUE obj; @@ -52,9 +64,8 @@ ast_new_internal(rb_ast_t *ast, NODE *node) return obj; } -static VALUE rb_ast_parse_str(VALUE str); -static VALUE rb_ast_parse_file(VALUE path); -static VALUE rb_ast_parse_array(VALUE array); +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) @@ -73,55 +84,34 @@ ast_parse_done(rb_ast_t *ast) return ast_new_internal(ast, (NODE *)ast->body.root); } -/* - * call-seq: - * RubyVM::AbstractSyntaxTree.parse(string) -> 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(NODE_SCOPE(0) 1:0, 1:9): > - */ static VALUE -rb_ast_s_parse(VALUE module, VALUE str) +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); + return rb_ast_parse_str(str, keep_script_lines, error_tolerant, keep_tokens); } static VALUE -rb_ast_parse_str(VALUE str) +rb_ast_parse_str(VALUE str, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { rb_ast_t *ast = 0; StringValue(str); - ast = rb_parser_compile_string_path(ast_parse_new(), Qnil, str, 1); + 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); } -/* - * call-seq: - * RubyVM::AbstractSyntaxTree.parse_file(pathname) -> RubyVM::AbstractSyntaxTree::Node - * - * Reads the file from <code>pathname</code>, then parses it like ::parse, - * returning the root node of the abstract syntax tree. - * - * SyntaxError is raised if <code>pathname</code>'s contents are not - * valid Ruby syntax. - * - * RubyVM::AbstractSyntaxTree.parse_file("my-app/app.rb") - * # => #<RubyVM::AbstractSyntaxTree::Node(NODE_SCOPE(0) 1:0, 31:3): > - */ static VALUE -rb_ast_s_parse_file(VALUE module, VALUE path) +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); + return rb_ast_parse_file(path, keep_script_lines, error_tolerant, keep_tokens); } static VALUE -rb_ast_parse_file(VALUE path) +rb_ast_parse_file(VALUE path, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { VALUE f; rb_ast_t *ast = 0; @@ -130,7 +120,11 @@ rb_ast_parse_file(VALUE path) FilePathValue(path); f = rb_file_open_str(path, "r"); rb_funcall(f, rb_intern("set_encoding"), 2, rb_enc_from_encoding(enc), rb_str_new_cstr("-")); - ast = rb_parser_compile_file_path(ast_parse_new(), Qnil, f, 1); + 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); } @@ -149,16 +143,20 @@ lex_array(VALUE array, int index) } static VALUE -rb_ast_parse_array(VALUE array) +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); - ast = rb_parser_compile_generic(ast_parse_new(), lex_array, Qnil, array, 1); + 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); } -static VALUE node_children(rb_ast_t*, NODE*); +static VALUE node_children(rb_ast_t*, const NODE*); static VALUE node_find(VALUE self, const int node_id) @@ -200,53 +198,70 @@ script_lines(VALUE path) return lines; } -/* - * call-seq: - * RubyVM::AbstractSyntaxTree.of(proc) -> RubyVM::AbstractSyntaxTree::Node - * RubyVM::AbstractSyntaxTree.of(method) -> RubyVM::AbstractSyntaxTree::Node - * - * Returns AST nodes of the given proc or method. - * - * RubyVM::AbstractSyntaxTree.of(proc {1 + 2}) - * # => #<RubyVM::AbstractSyntaxTree::Node(NODE_SCOPE(0) 1:35, 1:42): > - * - * def hello - * puts "hello, world" - * end - * - * RubyVM::AbstractSyntaxTree.of(method(:hello)) - * # => #<RubyVM::AbstractSyntaxTree::Node(NODE_SCOPE(0) 1:0, 3:3): > - */ static VALUE -rb_ast_s_of(VALUE module, VALUE body) +node_id_for_backtrace_location(rb_execution_context_t *ec, VALUE module, VALUE location) { - VALUE path, node, lines; int node_id; - const rb_iseq_t *iseq = NULL; - if (rb_obj_is_proc(body)) { - iseq = vm_proc_iseq(body); + if (!rb_frame_info_p(location)) { + rb_raise(rb_eTypeError, "Thread::Backtrace::Location object expected"); + } - if (!rb_obj_is_iseq((VALUE)iseq)) { - iseq = NULL; - } + 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; + int node_id; + + if (rb_frame_info_p(body)) { + iseq = rb_get_iseq_from_frame_info(body); + node_id = rb_get_node_id_from_frame_info(body); } else { - iseq = rb_method_iseq(body); + iseq = NULL; + + if (rb_obj_is_proc(body)) { + iseq = vm_proc_iseq(body); + + if (!rb_obj_is_iseq((VALUE)iseq)) return Qnil; + } + else { + iseq = rb_method_iseq(body); + } + if (iseq) { + node_id = ISEQ_BODY(iseq)->location.node_id; + } + } + + if (!iseq) { + return Qnil; } + lines = ISEQ_BODY(iseq)->variable.script_lines; - if (!iseq) return Qnil; + VALUE path = rb_iseq_path(iseq); + int e_option = RSTRING_LEN(path) == 2 && memcmp(RSTRING_PTR(path), "-e", 2) == 0; + + if (NIL_P(lines) && rb_iseq_from_eval_p(iseq) && !e_option) { + rb_raise(rb_eArgError, "cannot get AST for method defined in eval"); + } - path = rb_iseq_path(iseq); - node_id = iseq->body->location.node_id; - if (!NIL_P(lines = script_lines(path))) { - node = rb_ast_parse_array(lines); + if (!NIL_P(lines) || !NIL_P(lines = script_lines(path))) { + node = rb_ast_parse_array(lines, keep_script_lines, error_tolerant, keep_tokens); } - else if (RSTRING_LEN(path) == 2 && memcmp(RSTRING_PTR(path), "-e", 2) == 0) { - node = rb_ast_parse_str(rb_e_script); + else if (e_option) { + node = rb_ast_parse_str(rb_e_script, keep_script_lines, error_tolerant, keep_tokens); } else { - node = rb_ast_parse_file(path); + node = rb_ast_parse_file(path, keep_script_lines, error_tolerant, keep_tokens); } return node_find(node, node_id); @@ -267,19 +282,8 @@ node_type_to_str(const NODE *node) return (ruby_node_name(nd_type(node)) + rb_strlen_lit("NODE_")); } -/* - * call-seq: - * node.type -> symbol - * - * Returns the type of this node as a symbol. - * - * root = RubyVM::AbstractSyntaxTree.parse("x = 1 + 2") - * root.type # => :SCOPE - * call = root.children[2] - * call.type # => :OPCALL - */ static VALUE -rb_ast_node_type(VALUE self) +ast_node_type(rb_execution_context_t *ec, VALUE self) { struct ASTNodeData *data; TypedData_Get_Struct(self, struct ASTNodeData, &rb_node_type, data); @@ -287,6 +291,15 @@ rb_ast_node_type(VALUE self) return rb_sym_intern_ascii_cstr(node_type_to_str(data->node)); } +static VALUE +ast_node_node_id(rb_execution_context_t *ec, VALUE self) +{ + struct ASTNodeData *data; + TypedData_Get_Struct(self, struct ASTNodeData, &rb_node_type, data); + + return INT2FIX(nd_node_id(data->node)); +} + #define NEW_CHILD(ast, node) node ? ast_new_internal(ast, node) : Qnil static VALUE @@ -309,13 +322,13 @@ rb_ary_new_from_node_args(rb_ast_t *ast, long n, ...) } static VALUE -dump_block(rb_ast_t *ast, NODE *node) +dump_block(rb_ast_t *ast, const NODE *node) { VALUE ary = rb_ary_new(); do { rb_ary_push(ary, NEW_CHILD(ast, node->nd_head)); } while (node->nd_next && - nd_type(node->nd_next) == NODE_BLOCK && + nd_type_p(node->nd_next, NODE_BLOCK) && (node = node->nd_next, 1)); if (node->nd_next) { rb_ary_push(ary, NEW_CHILD(ast, node->nd_next)); @@ -325,12 +338,12 @@ dump_block(rb_ast_t *ast, NODE *node) } static VALUE -dump_array(rb_ast_t *ast, NODE *node) +dump_array(rb_ast_t *ast, const NODE *node) { VALUE ary = rb_ary_new(); rb_ary_push(ary, NEW_CHILD(ast, node->nd_head)); - while (node->nd_next && nd_type(node->nd_next) == NODE_ARRAY) { + while (node->nd_next && nd_type_p(node->nd_next, NODE_LIST)) { node = node->nd_next; rb_ary_push(ary, NEW_CHILD(ast, node->nd_head)); } @@ -348,7 +361,21 @@ var_name(ID id) } static VALUE -node_children(rb_ast_t *ast, NODE *node) +no_name_rest(void) +{ + ID rest; + CONST_ID(rest, "NODE_SPECIAL_NO_NAME_REST"); + return ID2SYM(rest); +} + +static VALUE +rest_arg(rb_ast_t *ast, const NODE *rest_arg) +{ + return NODE_NAMED_REST_P(rest_arg) ? NEW_CHILD(ast, rest_arg) : no_name_rest(); +} + +static VALUE +node_children(rb_ast_t *ast, const NODE *node) { char name[DECIMAL_SIZE_OF_BITS(sizeof(long) * CHAR_BIT) + 2]; /* including '$' */ @@ -364,24 +391,24 @@ node_children(rb_ast_t *ast, NODE *node) return rb_ary_new_from_node_args(ast, 2, node->nd_head, node->nd_body); case NODE_CASE2: return rb_ary_new_from_node_args(ast, 2, node->nd_head, node->nd_body); + case NODE_CASE3: + return rb_ary_new_from_node_args(ast, 2, node->nd_head, node->nd_body); case NODE_WHEN: return rb_ary_new_from_node_args(ast, 3, node->nd_head, node->nd_body, node->nd_next); + case NODE_IN: + return rb_ary_new_from_node_args(ast, 3, node->nd_head, node->nd_body, node->nd_next); case NODE_WHILE: - goto loop; case NODE_UNTIL: - loop: - return rb_ary_new_from_node_args(ast, 2, node->nd_cond, node->nd_body); + return rb_ary_push(rb_ary_new_from_node_args(ast, 2, node->nd_cond, node->nd_body), + RBOOL(node->nd_state)); case NODE_ITER: case NODE_FOR: return rb_ary_new_from_node_args(ast, 2, node->nd_iter, node->nd_body); case NODE_FOR_MASGN: return rb_ary_new_from_node_args(ast, 1, node->nd_var); case NODE_BREAK: - goto jump; case NODE_NEXT: - goto jump; case NODE_RETURN: - jump: return rb_ary_new_from_node_args(ast, 1, node->nd_stts); case NODE_REDO: return rb_ary_new_from_node_args(ast, 0); @@ -396,15 +423,13 @@ node_children(rb_ast_t *ast, NODE *node) case NODE_ENSURE: return rb_ary_new_from_node_args(ast, 2, node->nd_head, node->nd_ensr); case NODE_AND: - goto andor; case NODE_OR: - andor: { VALUE ary = rb_ary_new(); while (1) { rb_ary_push(ary, NEW_CHILD(ast, node->nd_1st)); - if (!node->nd_2nd || nd_type(node->nd_2nd) != (int)type) + if (!node->nd_2nd || !nd_type_p(node->nd_2nd, type)) break; node = node->nd_2nd; } @@ -415,23 +440,20 @@ node_children(rb_ast_t *ast, NODE *node) if (NODE_NAMED_REST_P(node->nd_args)) { return rb_ary_new_from_node_args(ast, 3, node->nd_value, node->nd_head, node->nd_args); } - return rb_ary_new_from_node_args(ast, 2, node->nd_value, node->nd_head); + else { + return rb_ary_new_from_args(3, NEW_CHILD(ast, node->nd_value), + NEW_CHILD(ast, node->nd_head), + no_name_rest()); + } case NODE_LASGN: - goto asgn; case NODE_DASGN: - goto asgn; - case NODE_DASGN_CURR: - goto asgn; case NODE_IASGN: - goto asgn; case NODE_CVASGN: - asgn: + case NODE_GASGN: if (NODE_REQUIRED_KEYWORD_P(node)) { - return rb_ary_new_from_args(1, var_name(node->nd_vid)); + return rb_ary_new_from_args(2, var_name(node->nd_vid), ID2SYM(rb_intern("NODE_SPECIAL_REQUIRED_KEYWORD"))); } return rb_ary_new_from_args(2, var_name(node->nd_vid), NEW_CHILD(ast, node->nd_value)); - case NODE_GASGN: - goto asgn; case NODE_CDECL: if (node->nd_vid) { return rb_ary_new_from_args(2, ID2SYM(node->nd_vid), NEW_CHILD(ast, node->nd_value)); @@ -443,9 +465,10 @@ node_children(rb_ast_t *ast, NODE *node) NEW_CHILD(ast, node->nd_args->nd_head), NEW_CHILD(ast, node->nd_args->nd_body)); case NODE_OP_ASGN2: - return rb_ary_new_from_args(4, NEW_CHILD(ast, node->nd_recv), - node->nd_next->nd_aid ? Qtrue : Qfalse, + return rb_ary_new_from_args(5, NEW_CHILD(ast, node->nd_recv), + RBOOL(node->nd_next->nd_aid), ID2SYM(node->nd_next->nd_vid), + ID2SYM(node->nd_next->nd_mid), NEW_CHILD(ast, node->nd_value)); case NODE_OP_ASGN_AND: return rb_ary_new_from_args(3, NEW_CHILD(ast, node->nd_head), ID2SYM(idANDOP), @@ -472,12 +495,10 @@ node_children(rb_ast_t *ast, NODE *node) return rb_ary_new_from_node_args(ast, 1, node->nd_args); case NODE_ZSUPER: return rb_ary_new_from_node_args(ast, 0); - case NODE_ARRAY: - goto ary; + case NODE_LIST: case NODE_VALUES: - ary: return dump_array(ast, node); - case NODE_ZARRAY: + case NODE_ZLIST: return rb_ary_new_from_node_args(ast, 0); case NODE_HASH: return rb_ary_new_from_node_args(ast, 1, node->nd_head); @@ -499,8 +520,6 @@ node_children(rb_ast_t *ast, NODE *node) name[1] = (char)node->nd_nth; name[2] = '\0'; return rb_ary_new_from_args(1, ID2SYM(rb_intern(name))); - case NODE_MATCH: - goto lit; case NODE_MATCH2: if (node->nd_args) { return rb_ary_new_from_node_args(ast, 3, node->nd_recv, node->nd_value, node->nd_args); @@ -508,26 +527,26 @@ node_children(rb_ast_t *ast, NODE *node) return rb_ary_new_from_node_args(ast, 2, node->nd_recv, node->nd_value); case NODE_MATCH3: return rb_ary_new_from_node_args(ast, 2, node->nd_recv, node->nd_value); + case NODE_MATCH: case NODE_LIT: - goto lit; case NODE_STR: - goto lit; case NODE_XSTR: - lit: return rb_ary_new_from_args(1, node->nd_lit); case NODE_ONCE: return rb_ary_new_from_node_args(ast, 1, node->nd_body); case NODE_DSTR: - goto dlit; case NODE_DXSTR: - goto dlit; case NODE_DREGX: - goto dlit; case NODE_DSYM: - dlit: - return rb_ary_new_from_args(3, node->nd_lit, - NEW_CHILD(ast, node->nd_next->nd_head), - NEW_CHILD(ast, node->nd_next->nd_next)); + { + NODE *n = node->nd_next; + VALUE head = Qnil, next = Qnil; + if (n) { + head = NEW_CHILD(ast, n->nd_head); + next = NEW_CHILD(ast, n->nd_next); + } + return rb_ary_new_from_args(3, node->nd_lit, head, next); + } case NODE_EVSTR: return rb_ary_new_from_node_args(ast, 1, node->nd_body); case NODE_ARGSCAT: @@ -559,13 +578,9 @@ node_children(rb_ast_t *ast, NODE *node) case NODE_COLON3: return rb_ary_new_from_args(1, ID2SYM(node->nd_mid)); case NODE_DOT2: - goto dot; case NODE_DOT3: - goto dot; case NODE_FLIP2: - goto dot; case NODE_FLIP3: - dot: return rb_ary_new_from_node_args(ast, 2, node->nd_beg, node->nd_end); case NODE_SELF: return rb_ary_new_from_node_args(ast, 0); @@ -593,7 +608,8 @@ node_children(rb_ast_t *ast, NODE *node) if (NODE_NAMED_REST_P(node->nd_1st)) { return rb_ary_new_from_node_args(ast, 2, node->nd_1st, node->nd_2nd); } - return rb_ary_new_from_node_args(ast, 1, node->nd_2nd); + return rb_ary_new_from_args(2, no_name_rest(), + NEW_CHILD(ast, node->nd_2nd)); case NODE_ARGS: { struct rb_args_info *ainfo = node->nd_ainfo; @@ -604,21 +620,56 @@ node_children(rb_ast_t *ast, NODE *node) var_name(ainfo->first_post_arg), INT2NUM(ainfo->post_args_num), NEW_CHILD(ast, ainfo->post_init), - var_name(ainfo->rest_arg), - NEW_CHILD(ast, ainfo->kw_args), - NEW_CHILD(ast, ainfo->kw_rest_arg), + (ainfo->rest_arg == NODE_SPECIAL_EXCESSIVE_COMMA + ? ID2SYM(rb_intern("NODE_SPECIAL_EXCESSIVE_COMMA")) + : var_name(ainfo->rest_arg)), + (ainfo->no_kwarg ? Qfalse : NEW_CHILD(ast, ainfo->kw_args)), + (ainfo->no_kwarg ? Qfalse : NEW_CHILD(ast, ainfo->kw_rest_arg)), var_name(ainfo->block_arg)); } case NODE_SCOPE: { - ID *tbl = node->nd_tbl; - int i, size = tbl ? (int)*tbl++ : 0; + rb_ast_id_table_t *tbl = node->nd_tbl; + int i, size = tbl ? tbl->size : 0; VALUE locals = rb_ary_new_capa(size); for (i = 0; i < size; i++) { - rb_ary_push(locals, var_name(tbl[i])); + rb_ary_push(locals, var_name(tbl->ids[i])); } return rb_ary_new_from_args(3, locals, NEW_CHILD(ast, node->nd_args), NEW_CHILD(ast, node->nd_body)); } + case NODE_ARYPTN: + { + struct rb_ary_pattern_info *apinfo = node->nd_apinfo; + VALUE rest = rest_arg(ast, apinfo->rest_arg); + return rb_ary_new_from_args(4, + NEW_CHILD(ast, node->nd_pconst), + NEW_CHILD(ast, apinfo->pre_args), + rest, + NEW_CHILD(ast, apinfo->post_args)); + } + case NODE_FNDPTN: + { + struct rb_fnd_pattern_info *fpinfo = node->nd_fpinfo; + VALUE pre_rest = rest_arg(ast, fpinfo->pre_rest_arg); + VALUE post_rest = rest_arg(ast, fpinfo->post_rest_arg); + return rb_ary_new_from_args(4, + NEW_CHILD(ast, node->nd_pconst), + pre_rest, + NEW_CHILD(ast, fpinfo->args), + post_rest); + } + case NODE_HSHPTN: + { + VALUE kwrest = node->nd_pkwrestarg == NODE_SPECIAL_NO_REST_KEYWORD ? ID2SYM(rb_intern("NODE_SPECIAL_NO_REST_KEYWORD")) : + NEW_CHILD(ast, node->nd_pkwrestarg); + + return rb_ary_new_from_args(3, + NEW_CHILD(ast, node->nd_pconst), + 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; @@ -627,17 +678,8 @@ node_children(rb_ast_t *ast, NODE *node) rb_bug("node_children: unknown node: %s", ruby_node_name(type)); } -/* - * call-seq: - * node.children -> array - * - * Returns AST nodes under this one. Each kind of node - * has different children, depending on what kind of node it is. - * - * The returned array may contain other nodes or <code>nil</code>. - */ static VALUE -rb_ast_node_children(VALUE self) +ast_node_children(rb_execution_context_t *ec, VALUE self) { struct ASTNodeData *data; TypedData_Get_Struct(self, struct ASTNodeData, &rb_node_type, data); @@ -645,14 +687,8 @@ rb_ast_node_children(VALUE self) return node_children(data->ast, data->node); } -/* - * call-seq: - * node.first_lineno -> integer - * - * The line number in the source code where this AST's text began. - */ static VALUE -rb_ast_node_first_lineno(VALUE self) +ast_node_first_lineno(rb_execution_context_t *ec, VALUE self) { struct ASTNodeData *data; TypedData_Get_Struct(self, struct ASTNodeData, &rb_node_type, data); @@ -660,14 +696,8 @@ rb_ast_node_first_lineno(VALUE self) return INT2NUM(nd_first_lineno(data->node)); } -/* - * call-seq: - * node.first_column -> integer - * - * The column number in the source code where this AST's text began. - */ static VALUE -rb_ast_node_first_column(VALUE self) +ast_node_first_column(rb_execution_context_t *ec, VALUE self) { struct ASTNodeData *data; TypedData_Get_Struct(self, struct ASTNodeData, &rb_node_type, data); @@ -675,14 +705,8 @@ rb_ast_node_first_column(VALUE self) return INT2NUM(nd_first_column(data->node)); } -/* - * call-seq: - * node.last_lineno -> integer - * - * The line number in the source code where this AST's text ended. - */ static VALUE -rb_ast_node_last_lineno(VALUE self) +ast_node_last_lineno(rb_execution_context_t *ec, VALUE self) { struct ASTNodeData *data; TypedData_Get_Struct(self, struct ASTNodeData, &rb_node_type, data); @@ -690,14 +714,8 @@ rb_ast_node_last_lineno(VALUE self) return INT2NUM(nd_last_lineno(data->node)); } -/* - * call-seq: - * node.last_column -> integer - * - * The column number in the source code where this AST's text ended. - */ static VALUE -rb_ast_node_last_column(VALUE self) +ast_node_last_column(rb_execution_context_t *ec, VALUE self) { struct ASTNodeData *data; TypedData_Get_Struct(self, struct ASTNodeData, &rb_node_type, data); @@ -705,14 +723,17 @@ rb_ast_node_last_column(VALUE self) return INT2NUM(nd_last_column(data->node)); } -/* - * call-seq: - * node.inspect -> string - * - * Returns debugging information about this node as a string. - */ static VALUE -rb_ast_node_inspect(VALUE self) +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; VALUE cname; @@ -731,30 +752,22 @@ rb_ast_node_inspect(VALUE self) return str; } +static VALUE +ast_node_script_lines(rb_execution_context_t *ec, VALUE self) +{ + struct ASTNodeData *data; + TypedData_Get_Struct(self, struct ASTNodeData, &rb_node_type, data); + VALUE ret = data->ast->body.script_lines; + if (!RB_TYPE_P(ret, T_ARRAY)) return Qnil; + return ret; +} + +#include "ast.rbinc" + void Init_ast(void) { - /* - * AbstractSyntaxTree provides methods to parse Ruby code into - * abstract syntax trees. The nodes in the tree - * are instances of RubyVM::AbstractSyntaxTree::Node. - */ rb_mAST = rb_define_module_under(rb_cRubyVM, "AbstractSyntaxTree"); - /* - * RubyVM::AbstractSyntaxTree::Node instances are created by parse methods in - * RubyVM::AbstractSyntaxTree. - */ rb_cNode = rb_define_class_under(rb_mAST, "Node", rb_cObject); - rb_undef_alloc_func(rb_cNode); - rb_define_singleton_method(rb_mAST, "parse", rb_ast_s_parse, 1); - rb_define_singleton_method(rb_mAST, "parse_file", rb_ast_s_parse_file, 1); - rb_define_singleton_method(rb_mAST, "of", rb_ast_s_of, 1); - rb_define_method(rb_cNode, "type", rb_ast_node_type, 0); - rb_define_method(rb_cNode, "first_lineno", rb_ast_node_first_lineno, 0); - rb_define_method(rb_cNode, "first_column", rb_ast_node_first_column, 0); - rb_define_method(rb_cNode, "last_lineno", rb_ast_node_last_lineno, 0); - rb_define_method(rb_cNode, "last_column", rb_ast_node_last_column, 0); - rb_define_method(rb_cNode, "children", rb_ast_node_children, 0); - rb_define_method(rb_cNode, "inspect", rb_ast_node_inspect, 0); } diff --git a/ast.rb b/ast.rb new file mode 100644 index 0000000000..f3f72c747f --- /dev/null +++ b/ast.rb @@ -0,0 +1,276 @@ +# for ast.c + +# AbstractSyntaxTree provides methods to parse Ruby code into +# abstract syntax trees. The nodes in the tree +# are instances of RubyVM::AbstractSyntaxTree::Node. +# +# This module is MRI specific as it exposes implementation details +# of the MRI abstract syntax tree. +# +# This module is experimental and its API is not stable, therefore it might +# change without notice. As examples, the order of children nodes is not +# guaranteed, the number of children nodes might change, there is no way to +# access children nodes by name, etc. +# +# If you are looking for a stable API or an API working under multiple Ruby +# implementations, consider using the _parser_ gem or Ripper. If you would +# like to make RubyVM::AbstractSyntaxTree stable, please join the discussion +# at https://bugs.ruby-lang.org/issues/14844. +# +module RubyVM::AbstractSyntaxTree + + # call-seq: + # 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. + # + # RubyVM::AbstractSyntaxTree.parse("x = 1 + 2") + # # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-1:9> + # + # 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, 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. + # + # SyntaxError is raised if _pathname_'s contents are not + # valid Ruby syntax. + # + # RubyVM::AbstractSyntaxTree.parse_file("my-app/app.rb") + # # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-31:3> + # + # 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, 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_. + # + # RubyVM::AbstractSyntaxTree.of(proc {1 + 2}) + # # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:35-1:42> + # + # def hello + # puts "hello, world" + # end + # + # RubyVM::AbstractSyntaxTree.of(method(:hello)) + # # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-3:3> + # + # 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 + # RubyVM::AbstractSyntaxTree. + # + # This class is MRI specific. + # + class Node + + # call-seq: + # node.type -> symbol + # + # Returns the type of this node as a symbol. + # + # root = RubyVM::AbstractSyntaxTree.parse("x = 1 + 2") + # root.type # => :SCOPE + # lasgn = root.children[2] + # lasgn.type # => :LASGN + # call = lasgn.children[1] + # call.type # => :OPCALL + def type + Primitive.ast_node_type + end + + # call-seq: + # node.first_lineno -> integer + # + # The line number in the source code where this AST's text began. + def first_lineno + Primitive.ast_node_first_lineno + end + + # call-seq: + # node.first_column -> integer + # + # The column number in the source code where this AST's text began. + def first_column + Primitive.ast_node_first_column + end + + # call-seq: + # node.last_lineno -> integer + # + # The line number in the source code where this AST's text ended. + def last_lineno + Primitive.ast_node_last_lineno + end + + # call-seq: + # node.last_column -> integer + # + # The column number in the source code where this AST's text ended. + def last_column + Primitive.ast_node_last_column + 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 + # has different children, depending on what kind of node it is. + # + # The returned array may contain other nodes or <code>nil</code>. + def children + Primitive.ast_node_children + end + + # call-seq: + # node.inspect -> string + # + # Returns debugging information about this node as a string. + def inspect + Primitive.ast_node_inspect + end + + # call-seq: + # node.node_id -> integer + # + # Returns an internal node_id number. + # Note that this is an API for ruby internal use, debugging, + # and research. Do not use this for any other purpose. + # The compatibility is not guaranteed. + def node_id + Primitive.ast_node_node_id + end + + # call-seq: + # node.script_lines -> array + # + # Returns the original source code as an array of lines. + # + # Note that this is an API for ruby internal use, debugging, + # and research. Do not use this for any other purpose. + # The compatibility is not guaranteed. + def script_lines + Primitive.ast_node_script_lines + end + + # call-seq: + # node.source -> string + # + # Returns the code fragment that corresponds to this AST. + # + # Note that this is an API for ruby internal use, debugging, + # and research. Do not use this for any other purpose. + # The compatibility is not guaranteed. + # + # Also note that this API may return an incomplete code fragment + # that does not parse; for example, a here document following + # an expression may be dropped. + def source + lines = script_lines + if lines + lines = lines[first_lineno - 1 .. last_lineno - 1] + lines[-1] = lines[-1][0...last_column] + lines[0] = lines[0][first_column..-1] + lines.join + else + nil + end + end + end +end diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000000..f8cdf3c0c1 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +PWD= +case "$0" in +*/*) srcdir=`dirname $0`;; +*) srcdir="";; +esac + +symlink='--install --symlink' +case " $* " in + *" -i "*|*" --install "*) + # reset to copy missing standard auxiliary files, instead of symlinks + symlink= + ;; +esac + +exec ${AUTORECONF:-autoreconf} ${symlink} "$@" ${srcdir:+"$srcdir"} diff --git a/basictest/test.rb b/basictest/test.rb index 25d52ca1ef..95875b52a6 100755 --- a/basictest/test.rb +++ b/basictest/test.rb @@ -35,7 +35,7 @@ class Progress # dircolors-like style colors = (colors = ENV['TEST_COLORS']) ? Hash[colors.scan(/(\w+)=([^:\n]*)/)] : {} begin - File.read(File.join(__dir__, "../test/colors")).scan(/(\w+)=([^:\n]*)/) do |n, c| + File.read(File.join(__dir__, "../tool/colors")).scan(/(\w+)=([^:\n]*)/) do |n, c| colors[n] ||= c end rescue @@ -984,13 +984,6 @@ test_ok($z == 0) test_check "iterator" -test_ok(!iterator?) - -def ttt - test_ok(iterator?) -end -ttt{} - # yield at top level test_ok(!defined?(yield)) @@ -1432,9 +1425,6 @@ marity_test(:test_ok) marity_test(:marity_test) marity_test(:p) -lambda(&method(:test_ok)).call(true) -lambda(&block_get{|a,n| test_ok(a,n)}).call(true, 2) - class ITER_TEST1 def a block_given? @@ -1739,7 +1729,7 @@ a = nil test_ok(defined?(a)) test_ok(a == nil) -# multiple asignment +# multiple assignment a, b = 1, 2 test_ok(a == 1 && b == 2) @@ -1970,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') @@ -2020,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 @@ -2147,7 +2140,7 @@ $_ = foobar test_ok($_ == foobar) class Gods - @@rule = "Uranus" # private to Gods + @@rule = "Uranus" def ruler0 @@rule end @@ -2170,7 +2163,7 @@ module Olympians end class Titans < Gods - @@rule = "Cronus" # do not affect @@rule in Gods + @@rule = "Cronus" # modifies @@rule in Gods include Olympians def ruler4 @@rule @@ -2185,7 +2178,14 @@ test_ok(Titans.ruler2 == "Cronus") atlas = Titans.new test_ok(atlas.ruler0 == "Cronus") test_ok(atlas.ruler3 == "Zeus") -test_ok(atlas.ruler4 == "Cronus") +begin + atlas.ruler4 +rescue RuntimeError => e + test_ok(e.message.include?("class variable @@rule of Olympians is overtaken by Gods")) +else + test_ok(false) +end +test_ok(atlas.ruler3 == "Zeus") test_check "trace" $x = 1234 diff --git a/benchmark/README.md b/benchmark/README.md index 24a2669143..e11381cad9 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -28,15 +28,17 @@ 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) + --timeout SECONDS Timeout ruby command execution with timeout(1) -v, --verbose Verbose mode. Multiple -v options increase visilibity (max: 2) ``` @@ -53,20 +55,20 @@ make benchmark # Or compare with specific ruby binary make benchmark COMPARE_RUBY="/path/to/ruby --jit" -# Run vm1 benchmarks -make benchmark ITEM=vm1 +# Run vm benchmarks +make benchmark ITEM=vm # Run some limited benchmarks in ITEM-matched files -make benchmark ITEM=vm1 OPTS=--filter=block +make benchmark ITEM=vm OPTS=--filter=block # You can specify the benchmark by an exact filename instead of using the default argument: # ARGS = $$(find $(srcdir)/benchmark -maxdepth 1 -name '*$(ITEM)*.yml' -o -name '*$(ITEM)*.rb') -make benchmark ARGS=../benchmark/erb_render.yml +make benchmark ARGS=benchmark/erb_render.yml # You can specify any option via $OPTS make benchmark OPTS="--help" # With `make benchmark`, some special runner plugins are available: # -r peak, -r size, -r total, -r utime, -r stime, -r cutime, -r cstime -make benchmark ITEM=vm2_bigarray OPTS="-r peak" +make benchmark ITEM=vm_bigarray OPTS="-r peak" ``` diff --git a/benchmark/app_aobench.rb b/benchmark/app_aobench.rb index 2bd6acfaf8..16296af12b 100644 --- a/benchmark/app_aobench.rb +++ b/benchmark/app_aobench.rb @@ -1,3 +1,5 @@ +# coding: US-ASCII + # AO render benchmark # Original program (C) Syoyo Fujita in Javascript (and other languages) # https://code.google.com/p/aobench/ @@ -9,6 +11,8 @@ IMAGE_HEIGHT = 256 NSUBSAMPLES = 2 NAO_SAMPLES = 8 +srand(0) + class Vec def initialize(x, y, z) @x = x @@ -278,12 +282,14 @@ end alias printf_orig printf def printf *args + # $fp.printf(*args) end # File.open("ao.ppm", "w") do |fp| + # $fp = fp printf("P6\n") printf("%d %d\n", IMAGE_WIDTH, IMAGE_HEIGHT) - printf("255\n", IMAGE_WIDTH, IMAGE_HEIGHT) + printf("255\n") Scene.new.render(IMAGE_WIDTH, IMAGE_HEIGHT, NSUBSAMPLES) # end diff --git a/benchmark/array_flatten.yml b/benchmark/array_flatten.yml new file mode 100644 index 0000000000..88ef544ba0 --- /dev/null +++ b/benchmark/array_flatten.yml @@ -0,0 +1,19 @@ +prelude: | + small_flat_ary = 5.times.to_a + large_flat_ary = 100.times.to_a + small_pairs_ary = [[1, 2]] * 5 + large_pairs_ary = [[1, 2]] * 100 + mostly_flat_ary = 100.times.to_a.push([101, 102]) + +benchmark: + small_flat_ary.flatten: small_flat_ary.flatten + small_flat_ary.flatten!: small_flat_ary.flatten! + large_flat_ary.flatten: large_flat_ary.flatten + large_flat_ary.flatten!: large_flat_ary.flatten! + small_pairs_ary.flatten: small_pairs_ary.flatten + small_pairs_ary.flatten!: small_pairs_ary.dup.flatten! + large_pairs_ary.flatten: large_pairs_ary.flatten + large_pairs_ary.flatten!: large_pairs_ary.dup.flatten! + mostly_flat_ary.flatten: mostly_flat_ary.flatten + mostly_flat_ary.flatten!: mostly_flat_ary.dup.flatten! +loop_count: 10000 diff --git a/benchmark/array_intersection.yml b/benchmark/array_intersection.yml new file mode 100644 index 0000000000..26705323fd --- /dev/null +++ b/benchmark/array_intersection.yml @@ -0,0 +1,14 @@ +prelude: | + small1 = [1, 2, 3] + small2 = [1, 2, 3, 4, 5] + small3 = [2, 3, 4, 5] + small4 = [2] + big1 = [1, 2, 3, 4] * 64 + big2 = [1, 2, 3] * 64 + big3 = [1, 2] * 64 + +benchmark: + small-&: small1 & small2 & small3 & small4 + small-intersection: small1.intersection(small2, small3, small4) + big-&: big1 & big2 & big3 + big-intersection: big1.intersection(big2, big3) diff --git a/benchmark/array_max_float.yml b/benchmark/array_max_float.yml new file mode 100644 index 0000000000..ace1ae2e14 --- /dev/null +++ b/benchmark/array_max_float.yml @@ -0,0 +1,30 @@ +prelude: | + ary2 = 2.times.map(&:to_f).shuffle + ary10 = 10.times.map(&:to_f).shuffle + ary100 = 100.times.map(&:to_f).shuffle + ary500 = 500.times.map(&:to_f).shuffle + ary1000 = 1000.times.map(&:to_f).shuffle + ary2000 = 2500.times.map(&:to_f).shuffle + ary3000 = 2500.times.map(&:to_f).shuffle + ary5000 = 5000.times.map(&:to_f).shuffle + ary10000 = 10000.times.map(&:to_f).shuffle + ary20000 = 20000.times.map(&:to_f).shuffle + ary50000 = 50000.times.map(&:to_f).shuffle + ary100000 = 100000.times.map(&:to_f).shuffle + +benchmark: + ary2.max: ary2.max + ary10.max: ary10.max + ary100.max: ary100.max + ary500.max: ary500.max + ary1000.max: ary1000.max + ary2000.max: ary2000.max + ary3000.max: ary3000.max + ary5000.max: ary5000.max + ary10000.max: ary10000.max + ary20000.max: ary20000.max + ary50000.max: ary50000.max + ary100000.max: ary100000.max + +loop_count: 10000 + diff --git a/benchmark/array_max_int.yml b/benchmark/array_max_int.yml new file mode 100644 index 0000000000..acd83684d0 --- /dev/null +++ b/benchmark/array_max_int.yml @@ -0,0 +1,31 @@ +prelude: | + ary2 = 2.times.to_a.shuffle + ary10 = 10.times.to_a.shuffle + ary100 = 100.times.to_a.shuffle + ary500 = 500.times.to_a.shuffle + ary1000 = 1000.times.to_a.shuffle + ary2000 = 2500.times.to_a.shuffle + ary3000 = 2500.times.to_a.shuffle + ary5000 = 5000.times.to_a.shuffle + ary10000 = 10000.times.to_a.shuffle + ary20000 = 20000.times.to_a.shuffle + ary50000 = 50000.times.to_a.shuffle + ary100000 = 100000.times.to_a.shuffle + ary1000000 = 1000000.times.to_a.shuffle + +benchmark: + ary2.max: ary2.max + ary10.max: ary10.max + ary100.max: ary100.max + ary500.max: ary500.max + ary1000.max: ary1000.max + ary2000.max: ary2000.max + ary3000.max: ary3000.max + ary5000.max: ary5000.max + ary10000.max: ary10000.max + ary20000.max: ary20000.max + ary50000.max: ary50000.max + ary100000.max: ary100000.max + ary1000000.max: ary1000000.max + +loop_count: 10000 diff --git a/benchmark/array_max_str.yml b/benchmark/array_max_str.yml new file mode 100644 index 0000000000..2aeed010f2 --- /dev/null +++ b/benchmark/array_max_str.yml @@ -0,0 +1,30 @@ +prelude: | + ary2 = 2.times.map(&:to_s).shuffle + ary10 = 10.times.map(&:to_s).shuffle + ary100 = 100.times.map(&:to_s).shuffle + ary500 = 500.times.map(&:to_s).shuffle + ary1000 = 1000.times.map(&:to_s).shuffle + ary2000 = 2500.times.map(&:to_s).shuffle + ary3000 = 2500.times.map(&:to_s).shuffle + ary5000 = 5000.times.map(&:to_s).shuffle + ary10000 = 10000.times.map(&:to_s).shuffle + ary20000 = 20000.times.map(&:to_s).shuffle + ary50000 = 50000.times.map(&:to_s).shuffle + ary100000 = 100000.times.map(&:to_s).shuffle + +benchmark: + ary2.max: ary2.max + ary10.max: ary10.max + ary100.max: ary100.max + ary500.max: ary500.max + ary1000.max: ary1000.max + ary2000.max: ary2000.max + ary3000.max: ary3000.max + ary5000.max: ary5000.max + ary10000.max: ary10000.max + ary20000.max: ary20000.max + ary50000.max: ary50000.max + ary100000.max: ary100000.max + +loop_count: 10000 + diff --git a/benchmark/array_min.yml b/benchmark/array_min.yml new file mode 100644 index 0000000000..53e5072b14 --- /dev/null +++ b/benchmark/array_min.yml @@ -0,0 +1,31 @@ +prelude: | + ary2 = 2.times.to_a.shuffle + ary10 = 10.times.to_a.shuffle + ary100 = 100.times.to_a.shuffle + ary500 = 500.times.to_a.shuffle + ary1000 = 1000.times.to_a.shuffle + ary2000 = 2500.times.to_a.shuffle + ary3000 = 2500.times.to_a.shuffle + ary5000 = 5000.times.to_a.shuffle + ary10000 = 10000.times.to_a.shuffle + ary20000 = 20000.times.to_a.shuffle + ary50000 = 50000.times.to_a.shuffle + ary100000 = 100000.times.to_a.shuffle + ary1000000 = 1000000.times.to_a.shuffle + +benchmark: + ary2.min: ary2.min + ary10.min: ary10.min + ary100.min: ary100.min + ary500.min: ary500.min + ary1000.min: ary1000.min + ary2000.min: ary2000.min + ary3000.min: ary3000.min + ary5000.min: ary5000.min + ary10000.min: ary10000.min + ary20000.min: ary20000.min + ary50000.min: ary50000.min + ary100000.min: ary100000.min + ary1000000.min: ary1000000.min + +loop_count: 10000 diff --git a/benchmark/array_sample.yml b/benchmark/array_sample.yml new file mode 100644 index 0000000000..1cd2b34794 --- /dev/null +++ b/benchmark/array_sample.yml @@ -0,0 +1,4 @@ +prelude: ary = (1..10_000).to_a +benchmark: + - ary.sample + - ary.sample(2) 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/attr_accessor.yml b/benchmark/attr_accessor.yml new file mode 100644 index 0000000000..82134cdf9b --- /dev/null +++ b/benchmark/attr_accessor.yml @@ -0,0 +1,29 @@ +prelude: | + class C + attr_accessor :x + def initialize + @x = nil + end + class_eval <<-END + def ar + #{'x;'*256} + end + def aw + #{'self.x = nil;'*256} + end + def arm + m = method(:x) + #{'m.call;'*256} + end + def awm + m = method(:x=) + #{'m.call(nil);'*256} + end + END + end + obj = C.new +benchmark: + attr_reader: "obj.ar" + attr_writer: "obj.aw" + attr_reader_method: "obj.arm" + attr_writer_method: "obj.awm" 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 new file mode 100644 index 0000000000..9e1f99d64e --- /dev/null +++ b/benchmark/buffer_get.yml @@ -0,0 +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: + 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 new file mode 100644 index 0000000000..655be9d7d8 --- /dev/null +++ b/benchmark/cgi_escape_html.yml @@ -0,0 +1,31 @@ +prelude: | + # frozen_string_literal: true + require 'cgi/escape' +benchmark: + - script: CGI.escapeHTML("") + loop_count: 20000000 + - script: CGI.escapeHTML("abcde") + loop_count: 20000000 + - script: CGI.escapeHTML("abcd<") + loop_count: 20000000 + - script: CGI.escapeHTML("'&\"<>") + loop_count: 5000000 + - prelude: long_no_escape = "abcde" * 300 + script: CGI.escapeHTML(long_no_escape) + loop_count: 1000000 + - prelude: long_all_escape = "'&\"<>" * 10 + script: CGI.escapeHTML(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: CGI.escapeHTML(example_html) + loop_count: 1000000 diff --git a/benchmark/complex_float_add.yml b/benchmark/complex_float_add.yml new file mode 100644 index 0000000000..d0150c5e5b --- /dev/null +++ b/benchmark/complex_float_add.yml @@ -0,0 +1,7 @@ +prelude: | + max, min = 1000.0, -1000.0 + a = Complex(rand(max)+min, rand(max)+min) + b = Complex(rand(max)+min, rand(max)+min) +benchmark: + complex_float_add: c = a + b +loop_count: 1000000 diff --git a/benchmark/complex_float_div.yml b/benchmark/complex_float_div.yml new file mode 100644 index 0000000000..b9f5e1d51c --- /dev/null +++ b/benchmark/complex_float_div.yml @@ -0,0 +1,7 @@ +prelude: | + max, min = 1000.0, -1000.0 + a = Complex(rand(max)+min, rand(max)+min) + b = Complex(rand(max)+min, rand(max)+min) +benchmark: + complex_float_div: c = a / b +loop_count: 1000000 diff --git a/benchmark/complex_float_mul.yml b/benchmark/complex_float_mul.yml new file mode 100644 index 0000000000..59b096a6dc --- /dev/null +++ b/benchmark/complex_float_mul.yml @@ -0,0 +1,7 @@ +prelude: | + max, min = 1000.0, -1000.0 + a = Complex(rand(max)+min, rand(max)+min) + b = Complex(rand(max)+min, rand(max)+min) +benchmark: + complex_float_mul: c = a * b +loop_count: 1000000 diff --git a/benchmark/complex_float_new.yml b/benchmark/complex_float_new.yml new file mode 100644 index 0000000000..6fcde3125b --- /dev/null +++ b/benchmark/complex_float_new.yml @@ -0,0 +1,7 @@ +prelude: | + max, min = 1000.0, -1000.0 + a = Complex(rand(max)+min, rand(max)+min) + b = Complex(rand(max)+min, rand(max)+min) +benchmark: + complex_float_new: c = Complex(a, b) +loop_count: 1000000 diff --git a/benchmark/complex_float_power.yml b/benchmark/complex_float_power.yml new file mode 100644 index 0000000000..c40a31ab55 --- /dev/null +++ b/benchmark/complex_float_power.yml @@ -0,0 +1,7 @@ +prelude: | + max, min = 1000.0, -1000.0 + a = Complex(rand(max)+min, rand(max)+min) + b = Complex(rand(max)+min, rand(max)+min) +benchmark: + complex_float_power: c = a ** b +loop_count: 1000000 diff --git a/benchmark/complex_float_sub.yml b/benchmark/complex_float_sub.yml new file mode 100644 index 0000000000..3fafe7cdbe --- /dev/null +++ b/benchmark/complex_float_sub.yml @@ -0,0 +1,7 @@ +prelude: | + max, min = 1000.0, -1000.0 + a = Complex(rand(max)+min, rand(max)+min) + b = Complex(rand(max)+min, rand(max)+min) +benchmark: + complex_float_sub: c = a - b +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_lazy_flat_map.yml b/benchmark/enum_lazy_flat_map.yml new file mode 100644 index 0000000000..0ee390a441 --- /dev/null +++ b/benchmark/enum_lazy_flat_map.yml @@ -0,0 +1,16 @@ +prelude: | + num = (1..).lazy.take(100) + ary2 = [[1,2]].cycle.lazy.take(10) + ary10 = [[*1..10]].cycle.lazy.take(10) + ary20 = [[*1..20]].cycle.lazy.take(10) + ary50 = [[*1..50]].cycle.lazy.take(10) + ary100 = [[*1..100]].cycle.lazy.take(10) + +benchmark: + num3: num.flat_map {|x| x}.take(3).to_a + num10: num.flat_map {|x| x}.take(3).to_a + ary2: ary2.flat_map {|x| x}.take(3).to_a + ary10: ary10.flat_map {|x| x}.take(3).to_a + ary20: ary20.flat_map {|x| x}.take(3).to_a + ary50: ary50.flat_map {|x| x}.take(3).to_a + ary100: ary100.flat_map {|x| x}.take(3).to_a diff --git a/benchmark/enum_lazy_zip.yml b/benchmark/enum_lazy_zip.yml new file mode 100644 index 0000000000..4566ff0261 --- /dev/null +++ b/benchmark/enum_lazy_zip.yml @@ -0,0 +1,22 @@ +prelude: | + a = (1..3).lazy + b = a.map {|x| x} + +benchmark: + first_ary: a.zip(["a", "b", "c"]).first + first_nonary: a.zip("a".."c").first + first_noarg: a.zip.first + + take3_ary: a.zip(["a", "b", "c"]).take(3).force + take3_nonary: a.zip("a".."c").take(3).force + take3_noarg: a.zip.take(3).force + + chain-first_ary: b.zip(["a", "b", "c"]).first + chain-first_nonary: b.zip("a".."c").first + chain-first_noarg: b.zip.first + + chain-take3_ary: b.zip(["a", "b", "c"]).take(3).force + chain-take3_nonary: b.zip("a".."c").take(3).force + chain-take3_noarg: b.zip.take(3).force + + block: a.zip("a".."c") {|x, y| [x, y]} 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/enum_tally.yml b/benchmark/enum_tally.yml new file mode 100644 index 0000000000..edd2e040a0 --- /dev/null +++ b/benchmark/enum_tally.yml @@ -0,0 +1,4 @@ +prelude: | + list = ("aaa".."zzz").to_a*10 +benchmark: + tally: list.tally 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/fiber_chain.rb b/benchmark/fiber_chain.rb deleted file mode 100755 index 7e0a7f9d45..0000000000 --- a/benchmark/fiber_chain.rb +++ /dev/null @@ -1,40 +0,0 @@ -# Check performance of fiber creation and transfer. - -def make_link(previous) - Fiber.new do - while message = previous.resume - Fiber.yield(message) - end - end -end - -def make_chain(length, &block) - chain = Fiber.new(&block) - - (length - 1).times do - chain = make_link(chain) - end - - return chain -end - -def run_benchmark(length, repeats, message = :hello) - chain = nil - - chain = make_chain(length) do - while true - Fiber.yield(message) - end - end - - repeats.times do - abort "invalid result" unless chain.resume == message - end -end - -n = (ARGV[0] || 1000).to_i -m = (ARGV[1] || 1000).to_i - -5.times do - run_benchmark(n, m) -end diff --git a/benchmark/fiber_chain.yml b/benchmark/fiber_chain.yml new file mode 100644 index 0000000000..a36c759f8e --- /dev/null +++ b/benchmark/fiber_chain.yml @@ -0,0 +1,36 @@ +prelude: | + def make_link(previous) + Fiber.new do + while message = previous.resume + Fiber.yield(message) + end + end + end + + def make_chain(length = 1000, &block) + chain = Fiber.new(&block) + + (length - 1).times do + chain = make_link(chain) + end + + return chain + end + + message = "Hello World!" + + chain = make_chain do + while true + Fiber.yield(message) + end + end +benchmark: + make_chain: | + make_chain(100) do + while true + Fiber.yield(message) + end + end + resume_chain: | + chain.resume +loop_count: 5000 diff --git a/benchmark/fiber_locals.yml b/benchmark/fiber_locals.yml new file mode 100644 index 0000000000..8588686477 --- /dev/null +++ b/benchmark/fiber_locals.yml @@ -0,0 +1,8 @@ +prelude: | + th = Thread.current + th[:key] = :val +benchmark: + key?: th.key?(:key) + []: th[:key] + keys: th.keys +loop_count: 1_000_000 diff --git a/benchmark/float_methods.yml b/benchmark/float_methods.yml new file mode 100644 index 0000000000..56ea41effc --- /dev/null +++ b/benchmark/float_methods.yml @@ -0,0 +1,14 @@ +prelude: | + flo = 4.2 +benchmark: + to_f: | + flo.to_f + abs: | + flo.abs + magnitude: | + flo.magnitude + -@: | + -flo + zero?: | + flo.zero? +loop_count: 20000000 diff --git a/benchmark/float_neg_posi.yml b/benchmark/float_neg_posi.yml new file mode 100644 index 0000000000..172db1bf6d --- /dev/null +++ b/benchmark/float_neg_posi.yml @@ -0,0 +1,8 @@ +prelude: | + flo = 4.2 +benchmark: + negative?: | + flo.negative? + positive?: | + flo.positive? +loop_count: 20000000 diff --git a/benchmark/float_to_s.yml b/benchmark/float_to_s.yml new file mode 100644 index 0000000000..0abae5cdb8 --- /dev/null +++ b/benchmark/float_to_s.yml @@ -0,0 +1,7 @@ +prelude: | + floats = [*0.0.step(1.0, 0.0001)] + +benchmark: + to_s: floats.each {|f| f.to_s} + +loop_count: 1000 diff --git a/benchmark/hash_aref_array.rb b/benchmark/hash_aref_array.rb new file mode 100644 index 0000000000..ac7a683d95 --- /dev/null +++ b/benchmark/hash_aref_array.rb @@ -0,0 +1,5 @@ +h = {} +arrays = (0..99).each_slice(10).to_a +#STDERR.puts arrays.inspect +arrays.each { |s| h[s] = s } +200_000.times { arrays.each { |s| h[s] } } diff --git a/benchmark/hash_defaults.yml b/benchmark/hash_defaults.yml new file mode 100644 index 0000000000..833f10e1c7 --- /dev/null +++ b/benchmark/hash_defaults.yml @@ -0,0 +1,6 @@ +prelude: | + h = Hash.new { :foo } +benchmark: + default_aref: h[1] + default_method: h.default(1) +loop_count: 1000000 diff --git a/benchmark/hash_dup.yml b/benchmark/hash_dup.yml new file mode 100644 index 0000000000..65f521ec94 --- /dev/null +++ b/benchmark/hash_dup.yml @@ -0,0 +1,8 @@ +prelude: | + small_hash = { a: 1 } + larger_hash = 20.times.map { |i| [('a'.ord + i).chr.to_sym, i] }.to_h + +benchmark: + dup_small: small_hash.dup + dup_larger: larger_hash.dup +loop_count: 10000 diff --git a/benchmark/hash_first.yml b/benchmark/hash_first.yml new file mode 100644 index 0000000000..c26df1a7ed --- /dev/null +++ b/benchmark/hash_first.yml @@ -0,0 +1,11 @@ +prelude: | + hash1 = 1_000_000.times.to_h { [rand, true]} + hash2 = hash1.dup + hash2.keys[1..100_000].each { hash2.delete _1 } + hash2.delete hash2.first[0] + +benchmark: + hash1: hash1.first + hash2: hash2.first + +loop_count: 100_000 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/irb_color.yml b/benchmark/irb_color.yml new file mode 100644 index 0000000000..ebdc8d7e8b --- /dev/null +++ b/benchmark/irb_color.yml @@ -0,0 +1,13 @@ +prelude: | + require 'irb/color' + code = <<~'CODE' + def self.foo # bar + :"erb #{ERB.new("<%= self %>", trim_mode: ?-).result}" + end + CODE +benchmark: + irb_color_complete: | + IRB::Color.colorize_code(code, complete: true) + irb_color_incomplete: | + IRB::Color.colorize_code(code, complete: false) +loop_count: 2000000 diff --git a/benchmark/irb_exec.yml b/benchmark/irb_exec.yml new file mode 100644 index 0000000000..28933f8b38 --- /dev/null +++ b/benchmark/irb_exec.yml @@ -0,0 +1,10 @@ +prelude: | + # frozen_string_literal: true + require 'rbconfig' + irb_f = [File.join(File.dirname(RbConfig.ruby), 'irb'), '-f'] +benchmark: + irb_exec: | + IO.popen(irb_f, 'w') do |io| + io.write('exit') + end +loop_count: 30 diff --git a/benchmark/iseq_load_from_binary.yml b/benchmark/iseq_load_from_binary.yml new file mode 100644 index 0000000000..7e9d73bdd4 --- /dev/null +++ b/benchmark/iseq_load_from_binary.yml @@ -0,0 +1,25 @@ +prelude: | + symbol = RubyVM::InstructionSequence.compile(":foo; :bar; :baz; :egg; :spam").to_binary + + define_method = RubyVM::InstructionSequence.compile(%{ + def foo; end + def bar; end + def baz; end + def egg; end + def spam; end + }).to_binary + + all = RubyVM::InstructionSequence.compile(%{ + module Foo; def foo; :foo; end; end + module Bar; def bar; :bar; end; end + module Baz; def baz; :baz; end; end + class Egg; def egg; :egg; end; end + class Spaml; def spam; :spam; end; end + }).to_binary + +benchmark: + symbol: RubyVM::InstructionSequence.load_from_binary(symbol) + define_method: RubyVM::InstructionSequence.load_from_binary(define_method) + all: RubyVM::InstructionSequence.load_from_binary(all) + +loop_count: 100_000 diff --git a/benchmark/ivar_extend.yml b/benchmark/ivar_extend.yml new file mode 100644 index 0000000000..eb9ee923f5 --- /dev/null +++ b/benchmark/ivar_extend.yml @@ -0,0 +1,23 @@ +prelude: | + class Embedded + def initialize + @a = 1 + @b = 1 + @c = 1 + end + end + + class Extended + def initialize + @a = 1 + @b = 1 + @c = 1 + @d = 1 + @e = 1 + @f = 1 + end + end +benchmark: + embedded: Embedded.new + extended: Extended.new +loop_count: 20_000_000 diff --git a/benchmark/kernel_clone.yml b/benchmark/kernel_clone.yml new file mode 100644 index 0000000000..069b23abcd --- /dev/null +++ b/benchmark/kernel_clone.yml @@ -0,0 +1,6 @@ +prelude: "object = Object.new" +benchmark: + clone: "object.clone" + clone_true: "object.clone(freeze: true)" + clone_false: "object.clone(freeze: false)" +loop_count: 10000 diff --git a/benchmark/kernel_float.yml b/benchmark/kernel_float.yml new file mode 100644 index 0000000000..215f6750fc --- /dev/null +++ b/benchmark/kernel_float.yml @@ -0,0 +1,5 @@ +benchmark: + float: "Float(42)" + float_true: "Float(42, exception: true)" + float_false: "Float(42, exception: false)" +loop_count: 10000 diff --git a/benchmark/kernel_tap.yml b/benchmark/kernel_tap.yml new file mode 100644 index 0000000000..4dcbb31b4d --- /dev/null +++ b/benchmark/kernel_tap.yml @@ -0,0 +1,6 @@ +prelude: | + obj = Object.new + x = nil +benchmark: + kernel_tap: obj.tap { |o| x = o } +loop_count: 20000000 diff --git a/benchmark/kernel_then.yml b/benchmark/kernel_then.yml new file mode 100644 index 0000000000..85f7341e33 --- /dev/null +++ b/benchmark/kernel_then.yml @@ -0,0 +1,6 @@ +benchmark: + kernel_then: 1.then { |i| i + 1 } + kernel_then_enum: 1.then + kernel_yield_self: 1.yield_self { |i| i + 1 } + kernel_yield_self_enum: 1.yield_self +loop_count: 20000000 diff --git a/benchmark/keyword_arguments.yml b/benchmark/keyword_arguments.yml new file mode 100644 index 0000000000..fce6bce0b8 --- /dev/null +++ b/benchmark/keyword_arguments.yml @@ -0,0 +1,13 @@ +prelude: | + h = {a: 1} + def kw(a: 1) a end + def kws(**kw) kw end +benchmark: + kw_to_kw: "kw(a: 1)" + kw_splat_to_kw: "kw(**h)" + kw_to_kw_splat: "kws(a: 1)" + kw_splat_to_kw_splat: "kws(**h)" + kw_and_splat_to_kw: "kw(a: 1, **h)" + kw_splats_to_kw: "kw(**h, **h)" + kw_and_splat_to_kw_splat: "kws(a: 1, **h)" + kw_splats_to_kw_splat: "kws(**h, **h)" diff --git a/benchmark/lib/benchmark_driver/runner/mjit.rb b/benchmark/lib/benchmark_driver/runner/mjit.rb new file mode 100644 index 0000000000..3a58a620de --- /dev/null +++ b/benchmark/lib/benchmark_driver/runner/mjit.rb @@ -0,0 +1,34 @@ +require 'benchmark_driver/struct' +require 'benchmark_driver/metric' +require 'erb' + +# A runner to measure after-JIT performance easily +class BenchmarkDriver::Runner::Mjit < BenchmarkDriver::Runner::Ips + # JobParser returns this, `BenchmarkDriver::Runner.runner_for` searches "*::Job" + Job = Class.new(BenchmarkDriver::DefaultJob) + + # Dynamically fetched and used by `BenchmarkDriver::JobParser.parse` + JobParser = BenchmarkDriver::DefaultJobParser.for(klass: Job, metrics: [METRIC]).extend(Module.new{ + def parse(**) + jobs = super + jobs.map do |job| + job = job.dup + job.prelude = "#{job.prelude}\n#{<<~EOS}" + if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? + __bmdv_ruby_i = 0 + while __bmdv_ruby_i < 10000 # MJIT call threshold + #{job.script} + __bmdv_ruby_i += 1 + end + RubyVM::MJIT.pause # compile + #{job.script} + RubyVM::MJIT.resume; RubyVM::MJIT.pause # recompile + #{job.script} + RubyVM::MJIT.resume; RubyVM::MJIT.pause # recompile 2 + end + EOS + job + end + end + }) +end diff --git a/benchmark/lib/benchmark_driver/runner/peak.rb b/benchmark/lib/benchmark_driver/runner/peak.rb index 0ba8af42c5..d04f2e51ff 100644 --- a/benchmark/lib/benchmark_driver/runner/peak.rb +++ b/benchmark/lib/benchmark_driver/runner/peak.rb @@ -61,7 +61,7 @@ class BenchmarkDriver::Runner::Peak loop_count: job.loop_count, ) - memory_status = File.expand_path('../../../../test/lib/memory_status', __dir__) + memory_status = File.expand_path('../../../../tool/lib/memory_status', __dir__) Tempfile.open(['benchmark_driver-', '.rb']) do |f| with_script(benchmark.render) do |path| output = IO.popen([*context.executable.command, path, f.path, target, memory_status], &:read) diff --git a/benchmark/lib/benchmark_driver/runner/ractor.rb b/benchmark/lib/benchmark_driver/runner/ractor.rb new file mode 100644 index 0000000000..c730b8e4a5 --- /dev/null +++ b/benchmark/lib/benchmark_driver/runner/ractor.rb @@ -0,0 +1,122 @@ +require 'erb' + +# A runner to measure performance *inside* Ractor +class BenchmarkDriver::Runner::Ractor < BenchmarkDriver::Runner::Ips + # JobParser returns this, `BenchmarkDriver::Runner.runner_for` searches "*::Job" + Job = Class.new(BenchmarkDriver::DefaultJob) do + attr_accessor :ractor + end + + # Dynamically fetched and used by `BenchmarkDriver::JobParser.parse` + JobParser = BenchmarkDriver::DefaultJobParser.for(klass: Job, metrics: [METRIC]).extend(Module.new{ + def parse(ractor: 1, **kwargs) + super(**kwargs).each do |job| + job.ractor = ractor + end + end + }) + + private + + unless private_instance_methods.include?(:run_benchmark) + raise "#run_benchmark is no longer defined in BenchmarkDriver::Runner::Ips" + end + + # @param [BenchmarkDriver::Runner::Ips::Job] job - loop_count is not nil + # @param [BenchmarkDriver::Context] context + # @return [BenchmarkDriver::Metrics] + def run_benchmark(job, context:) + benchmark = BenchmarkScript.new( + preludes: [context.prelude, job.prelude], + script: job.script, + teardown: job.teardown, + loop_count: job.loop_count, + ) + + results = job.ractor.times.map do + Tempfile.open('benchmark_driver_result') + end + duration = with_script(benchmark.render(results: results.map(&:path))) do |path| + success = execute(*context.executable.command, path, exception: false) + if success && ((value = results.map { |f| Float(f.read) }.max) > 0) + value + else + BenchmarkDriver::Result::ERROR + end + end + results.each(&:close) + + value_duration( + loop_count: job.loop_count, + duration: duration, + ) + end + + # @param [String] prelude + # @param [String] script + # @param [String] teardown + # @param [Integer] loop_count + BenchmarkScript = ::BenchmarkDriver::Struct.new(:preludes, :script, :teardown, :loop_count) do + # @param [String] result - A file to write result + def render(results:) + prelude = preludes.reject(&:nil?).reject(&:empty?).join("\n") + ERB.new(<<-RUBY).result_with_hash(results: results) +Warning[:experimental] = false +# shareable-constant-value: experimental_everything +#{prelude} + +if #{loop_count} == 1 + __bmdv_loop_before = 0 + __bmdv_loop_after = 0 +else + __bmdv_loop_before = Time.new + #{while_loop('', loop_count, id: 0)} + __bmdv_loop_after = Time.new +end + +__bmdv_ractors = [] +<% results.size.times do %> +__bmdv_ractors << Ractor.new(__bmdv_loop_after - __bmdv_loop_before) { |__bmdv_loop_time| + __bmdv_time = Time + __bmdv_script_before = __bmdv_time.new + #{while_loop(script, loop_count, id: 1)} + __bmdv_script_after = __bmdv_time.new + + (__bmdv_script_after - __bmdv_script_before) - __bmdv_loop_time +} +<% end %> + +# Wait for all Ractors before executing code to write results +__bmdv_ractors.map!(&:take) + +<% results.each do |result| %> +File.write(<%= result.dump %>, __bmdv_ractors.shift) +<% end %> + +#{teardown} + RUBY + end + + private + + # id is to prevent: + # can not isolate a Proc because it accesses outer variables (__bmdv_i) + def while_loop(content, times, id:) + if !times.is_a?(Integer) || times <= 0 + raise ArgumentError.new("Unexpected times: #{times.inspect}") + elsif times == 1 + return content + end + + # TODO: execute in batch + <<-RUBY +__bmdv_i#{id} = 0 +while __bmdv_i#{id} < #{times} + #{content} + __bmdv_i#{id} += 1 +end + RUBY + end + end + private_constant :BenchmarkScript +end diff --git a/benchmark/lib/load.rb b/benchmark/lib/load.rb index 4d73a63323..31b770c484 100755..100644 --- a/benchmark/lib/load.rb +++ b/benchmark/lib/load.rb @@ -1,2 +1,18 @@ +# How to use this file: +# 1. write a `$(srcdir)/test.rb` like: +=begin +require_relative 'benchmark/lib/load' + +Benchmark.driver(repeat_count: 5){|x| + x.executable name: 'clean-miniruby', command: %w'../clean-trunk/miniruby' + x.executable name: 'modif-miniruby', command: %w'./miniruby' + + x.report %q{ + h = {a: 1, b: 2, c: 3, d: 4} + } +} +=end +# +# 2. `make run` $:.unshift(File.join(__dir__, '../benchmark-driver/lib')) require 'benchmark_driver' diff --git a/benchmark/marshal_dump_load_integer.yml b/benchmark/marshal_dump_load_integer.yml new file mode 100644 index 0000000000..78ebf823d2 --- /dev/null +++ b/benchmark/marshal_dump_load_integer.yml @@ -0,0 +1,22 @@ +prelude: | + smallint_array = 1000.times.map { |x| x } + bigint32_array = 1000.times.map { |x| x + 2**32 } + bigint64_array = 1000.times.map { |x| x + 2**64 } + + smallint_dump = Marshal.dump(smallint_array) + bigint32_dump = Marshal.dump(bigint32_array) + bigint64_dump = Marshal.dump(bigint64_array) +benchmark: + marshal_dump_integer_small: | + Marshal.dump(smallint_array) + marshal_dump_integer_over_32_bit: | + Marshal.dump(bigint32_array) + marshal_dump_integer_over_64_bit: | + Marshal.dump(bigint64_array) + marshal_load_integer_small: | + Marshal.load(smallint_dump) + marshal_load_integer_over_32_bit: | + Marshal.load(bigint32_dump) + marshal_load_integer_over_64_bit: | + Marshal.load(bigint64_dump) +loop_count: 4000 diff --git a/benchmark/masgn.yml b/benchmark/masgn.yml new file mode 100644 index 0000000000..31cb8ee4a3 --- /dev/null +++ b/benchmark/masgn.yml @@ -0,0 +1,53 @@ +prelude: | + a = [nil] * 3 + b = Class.new{attr_writer :a, :b, :c}.new + c = d = e = f = g = h = i = nil +benchmark: + array2_2: "c = (a[0], a[1] = 1, 2)" + array2_3: "c = (a[0], a[1] = 1, 2, 3)" + array3_2: "c = (a[0], a[1], a[2] = 1, 2)" + array3_3: "c = (a[0], a[1], a[2] = 1, 2, 3)" + attr2_2: "c = (b.a, b.b = 1, 2)" + attr2_3: "c = (b.a, b.b = 1, 2, 3)" + attr3_2: "c = (b.a, b.b, b.c = 1, 2)" + attr3_3: "c = (b.a, b.b, b.c = 1, 2, 3)" + lvar2_2: "c = (d, e = 1, 2)" + lvar2_3: "c = (d, e = 1, 2, 3)" + lvar3_2: "c = (d, e, f = 1, 2)" + lvar3_3: "c = (d, e, f = 1, 2, 3)" + array2_2p: "(a[0], a[1] = 1, 2; nil)" + array2_3p: "(a[0], a[1] = 1, 2, 3; nil)" + array3_2p: "(a[0], a[1], a[2] = 1, 2; nil)" + array3_3p: "(a[0], a[1], a[2] = 1, 2, 3; nil)" + attr2_2p: "(b.a, b.b = 1, 2; nil)" + attr2_3p: "(b.a, b.b = 1, 2, 3; nil)" + attr3_2p: "(b.a, b.b, b.c = 1, 2; nil)" + attr3_3p: "(b.a, b.b, b.c = 1, 2, 3; nil)" + lvar2_2p: "(d, e = 1, 2; nil)" + lvar2_3p: "(d, e = 1, 2, 3; nil)" + lvar3_2p: "(d, e, f = 1, 2; nil)" + lvar3_3p: "(d, e, f = 1, 2, 3; nil)" + array2_2lv: "c = (a[0], a[1] = g, h)" + array2_ilv: "c = (a[0], a[1] = g, h, i)" + arrayi_2lv: "c = (a[0], a[1], a[2] = g, h)" + arrayi_ilv: "c = (a[0], a[1], a[2] = g, h, i)" + attr2_2lv: "c = (b.a, b.b = g, h)" + attr2_ilv: "c = (b.a, b.b = g, h, i)" + attri_2lv: "c = (b.a, b.b, b.c = g, h)" + attri_ilv: "c = (b.a, b.b, b.c = g, h, i)" + lvar2_2lv: "c = (d, e = g, h)" + lvar2_ilv: "c = (d, e = g, h, i)" + lvari_2lv: "c = (d, e, f = g, h)" + lvari_ilv: "c = (d, e, f = g, h, i)" + array2_2plv: "(a[0], a[1] = g, h; nil)" + array2_iplv: "(a[0], a[1] = g, h, i; nil)" + arrayi_2plv: "(a[0], a[1], a[2] = g, h; nil)" + arrayi_iplv: "(a[0], a[1], a[2] = g, h, i; nil)" + attr2_2plv: "(b.a, b.b = g, h; nil)" + attr2_iplv: "(b.a, b.b = g, h, i; nil)" + attri_2plv: "(b.a, b.b, b.c = g, h; nil)" + attri_iplv: "(b.a, b.b, b.c = g, h, i; nil)" + lvar2_2plv: "(d, e = g, h; nil)" + lvar2_iplv: "(d, e = g, h, i; nil)" + lvari_2plv: "(d, e, f = g, h; nil)" + lvari_iplv: "(d, e, f = g, h, i; nil)" diff --git a/benchmark/match_gt4.rb b/benchmark/match_gt4.rb new file mode 100644 index 0000000000..ffda109912 --- /dev/null +++ b/benchmark/match_gt4.rb @@ -0,0 +1 @@ +1000000.times { /(.)(.)(\d+)(\d)/.match("THX1138.") } diff --git a/benchmark/match_small.rb b/benchmark/match_small.rb new file mode 100644 index 0000000000..3b743d484a --- /dev/null +++ b/benchmark/match_small.rb @@ -0,0 +1 @@ +1000000.times { 'haystack'.match(/hay/) } diff --git a/benchmark/method_bind_call.yml b/benchmark/method_bind_call.yml new file mode 100644 index 0000000000..9e0e046ed4 --- /dev/null +++ b/benchmark/method_bind_call.yml @@ -0,0 +1,16 @@ +prelude: | + named_module = Kernel + + module FakeName + def self.name + "NotMyame".freeze + end + end + + MOD_NAME = Module.instance_method(:name) + +benchmark: + fastpath: MOD_NAME.bind_call(Kernel) + slowpath: MOD_NAME.bind_call(FakeName) + +loop_count: 100_000 diff --git a/benchmark/mjit_exivar.yml b/benchmark/mjit_exivar.yml new file mode 100644 index 0000000000..2584fa6410 --- /dev/null +++ b/benchmark/mjit_exivar.yml @@ -0,0 +1,18 @@ +type: lib/benchmark_driver/runner/mjit +prelude: | + class Bench < Hash + def initialize + @exivar = nil + end + + def exivar + @exivar + end + end + + bench = Bench.new + +benchmark: + mjit_exivar: bench.exivar + +loop_count: 200000000 diff --git a/benchmark/mjit_integer.yml b/benchmark/mjit_integer.yml new file mode 100644 index 0000000000..a6b5c9ee16 --- /dev/null +++ b/benchmark/mjit_integer.yml @@ -0,0 +1,32 @@ +type: lib/benchmark_driver/runner/mjit +prelude: | + def mjit_abs(int) int.abs end + def mjit_bit_length(int) int.bit_length end + def mjit_comp(int) ~int end + def mjit_even?(int) int.even? end + def mjit_integer?(int) int.integer? end + def mjit_magnitude(int) int.magnitude end + def mjit_odd?(int) int.odd? end + def mjit_ord(int) int.ord end + def mjit_size(int) int.size end + def mjit_to_i(int) int.to_i end + def mjit_to_int(int) int.to_int end + def mjit_uminus(int) -int end + def mjit_zero?(int) int.zero? end + +benchmark: + - mjit_abs(-1) + - mjit_bit_length(100) + - mjit_comp(1) + - mjit_even?(2) + - mjit_integer?(0) + - mjit_magnitude(-1) + - mjit_odd?(1) + - mjit_ord(1) + - mjit_size(1) + - mjit_to_i(1) + - mjit_to_int(1) + - mjit_uminus(1) + - mjit_zero?(0) + +loop_count: 40000000 diff --git a/benchmark/mjit_kernel.yml b/benchmark/mjit_kernel.yml new file mode 100644 index 0000000000..7720e65c2c --- /dev/null +++ b/benchmark/mjit_kernel.yml @@ -0,0 +1,20 @@ +type: lib/benchmark_driver/runner/mjit +prelude: | + def mjit_class(obj) + obj.class + end + + def mjit_frozen?(obj) + obj.frozen? + end + + str = "" + fstr = "".freeze + +benchmark: + - mjit_class(self) + - mjit_class(1) + - mjit_frozen?(str) + - mjit_frozen?(fstr) + +loop_count: 40000000 diff --git a/benchmark/mjit_leave.yml b/benchmark/mjit_leave.yml new file mode 100644 index 0000000000..9ac68b164b --- /dev/null +++ b/benchmark/mjit_leave.yml @@ -0,0 +1,8 @@ +type: lib/benchmark_driver/runner/mjit +prelude: | + def leave + nil + end +benchmark: + mjit_leave: leave +loop_count: 200000000 diff --git a/benchmark/mjit_opt_cc_insns.yml b/benchmark/mjit_opt_cc_insns.yml new file mode 100644 index 0000000000..fed6d34bd5 --- /dev/null +++ b/benchmark/mjit_opt_cc_insns.yml @@ -0,0 +1,27 @@ +# opt_* insns using vm_method_cfunc_is with send-compatible operands: +# * opt_nil_p +# * opt_not +# * opt_eq +type: lib/benchmark_driver/runner/mjit +prelude: | + def mjit_nil?(obj) + obj.nil? + end + + def mjit_not(obj) + !obj + end + + def mjit_eq(a, b) + a == b + end + +benchmark: + - script: mjit_nil?(1) + loop_count: 40000000 + - script: mjit_not(1) + loop_count: 40000000 + - script: mjit_eq(1, nil) + loop_count: 8000000 + - script: mjit_eq(nil, 1) + loop_count: 8000000 diff --git a/benchmark/mjit_struct_aref.yml b/benchmark/mjit_struct_aref.yml new file mode 100644 index 0000000000..bfba1323f2 --- /dev/null +++ b/benchmark/mjit_struct_aref.yml @@ -0,0 +1,10 @@ +type: lib/benchmark_driver/runner/mjit +prelude: | + def mjit_struct_aref(struct) + struct.aa + end + struct = Struct.new(:a0, :a1, :a2, :a3, :a4, :a5, :a6, :a7, :a8, :a9, :aa).new + +benchmark: mjit_struct_aref(struct) + +loop_count: 40000000 diff --git a/benchmark/module_eqq.yml b/benchmark/module_eqq.yml new file mode 100644 index 0000000000..a561fb86dc --- /dev/null +++ b/benchmark/module_eqq.yml @@ -0,0 +1,27 @@ +prelude: | + class SimpleClass; end + class MediumClass + 10.times { include Module.new } + end + class LargeClass + 100.times { include Module.new } + end + class HugeClass + 300.times { include Module.new } + end + SimpleObj = SimpleClass.new + MediumObj = MediumClass.new + LargeObj = LargeClass.new + HugeObj = HugeClass.new +benchmark: + simple_class_eqq_simple_obj: | + SimpleClass === SimpleObj + medium_class_eqq_simple_obj: | + MediumClass === SimpleObj + simple_class_eqq_medium_obj: | + SimpleClass === MediumObj + simple_class_eqq_large_obj: | + SimpleClass === LargeObj + simple_class_eqq_huge_obj: | + SimpleClass === HugeObj +loop_count: 20000000 diff --git a/benchmark/nil_p.yml b/benchmark/nil_p.yml new file mode 100644 index 0000000000..79ba4f2177 --- /dev/null +++ b/benchmark/nil_p.yml @@ -0,0 +1,9 @@ +prelude: | + class Niller; def nil?; true; end; end + xnil, notnil = nil, Object.new + niller = Niller.new +benchmark: + - xnil.nil? + - notnil.nil? + - niller.nil? +loop_count: 10000000 diff --git a/benchmark/nilclass.yml b/benchmark/nilclass.yml new file mode 100644 index 0000000000..fba67a5f6a --- /dev/null +++ b/benchmark/nilclass.yml @@ -0,0 +1,6 @@ +benchmark: + to_i: | + nil.to_i + to_f: | + nil.to_f +loop_count: 100000 diff --git a/benchmark/num_zero_p.yml b/benchmark/num_zero_p.yml new file mode 100644 index 0000000000..2195963433 --- /dev/null +++ b/benchmark/num_zero_p.yml @@ -0,0 +1,8 @@ +benchmark: + - 0.zero? + - 1.zero? + - 0r.zero? + - 1r.zero? + - 0i.zero? + - 1i.zero? +loop_count: 50000000 diff --git a/benchmark/numeric_methods.yml b/benchmark/numeric_methods.yml new file mode 100644 index 0000000000..1384902935 --- /dev/null +++ b/benchmark/numeric_methods.yml @@ -0,0 +1,29 @@ +prelude: | + int = 42 + flo = 4.2 +benchmark: + real?: | + int.real? + integer?: | + flo.integer? + finite?: | + int.finite? + infinite?: | + int.infinite? + integer_real: | + int.real + float_real: | + flo.real + integr_imag: | + int.imag + float_imag: | + flo.imag + integer_conj: | + int.conj + float_conj: | + flo.conj + integer_numerator: | + int.numerator + integer_denominator: | + int.denominator +loop_count: 20000000 diff --git a/benchmark/object_allocate.yml b/benchmark/object_allocate.yml new file mode 100644 index 0000000000..93ff463e41 --- /dev/null +++ b/benchmark/object_allocate.yml @@ -0,0 +1,21 @@ +prelude: | + class Eight + 8.times { include(Module.new) } + end + class ThirtyTwo + 32.times { include(Module.new) } + end + class SixtyFour + 64.times { include(Module.new) } + end + class OneTwentyEight + 128.times { include(Module.new) } + end + # Disable GC to see raw throughput: + GC.disable +benchmark: + allocate_8_deep: Eight.new + allocate_32_deep: ThirtyTwo.new + allocate_64_deep: SixtyFour.new + allocate_128_deep: OneTwentyEight.new +loop_count: 100000 diff --git a/benchmark/objspace_dump_all.yml b/benchmark/objspace_dump_all.yml new file mode 100644 index 0000000000..ebab562d2e --- /dev/null +++ b/benchmark/objspace_dump_all.yml @@ -0,0 +1,13 @@ +prelude: | + require 'objspace' + require 'tempfile' + $objs = 1_000.times.map { Object.new } + $strings = 1_000.times.map { |i| "string #{i}" } + $file = Tempfile.new('heap') + $dev_null = File.open(File::NULL, 'w+') + +benchmark: + dump_all_string: "ObjectSpace.dump_all(output: :string)" + dump_all_file: "ObjectSpace.dump_all(output: $file)" + dump_all_dev_null: "ObjectSpace.dump_all(output: $dev_null)" +loop_count: 1 diff --git a/benchmark/other-lang/fact.py b/benchmark/other-lang/fact.py index 01593965d9..1ce9f76275 100644 --- a/benchmark/other-lang/fact.py +++ b/benchmark/other-lang/fact.py @@ -3,7 +3,7 @@ def factL(n): r = 1 - for x in range(2, n): + for x in range(2, n+1): r *= x return r diff --git a/benchmark/pm_array.yml b/benchmark/pm_array.yml new file mode 100644 index 0000000000..babb65a289 --- /dev/null +++ b/benchmark/pm_array.yml @@ -0,0 +1,19 @@ +prelude: | + def call(*val) + case val + in [String => body] + [200, {}, [body]] + in [Integer => status] + [status, {}, [""]] + in [Integer, String] => response + [response[0], {}, [response[1]]] + in [Integer, Hash, String] => response + [response[0], response[1], [response[2]]] + end + end + +benchmark: + first_match: call("ok") + second_match: call(401) + third_match: call(200, "ok") + fourth_match: call(201, {}, "created") diff --git a/benchmark/ractor_const.yml b/benchmark/ractor_const.yml new file mode 100644 index 0000000000..d7ab74bdca --- /dev/null +++ b/benchmark/ractor_const.yml @@ -0,0 +1,4 @@ +type: lib/benchmark_driver/runner/ractor +benchmark: + ractor_const: Object +ractor: 1 diff --git a/benchmark/ractor_float_to_s.yml b/benchmark/ractor_float_to_s.yml new file mode 100644 index 0000000000..8f492be668 --- /dev/null +++ b/benchmark/ractor_float_to_s.yml @@ -0,0 +1,8 @@ +type: lib/benchmark_driver/runner/ractor +prelude: | + FLOATS = [*0.0.step(1.0, 0.001)] +benchmark: + ractor_float_to_s: | + FLOATS.each {|f| f.to_s} +loop_count: 100 +ractor: 2 diff --git a/benchmark/range_last.yml b/benchmark/range_last.yml new file mode 100644 index 0000000000..a6674f82ee --- /dev/null +++ b/benchmark/range_last.yml @@ -0,0 +1,4 @@ +benchmark: + - (1..1_000_000).last(100) + - (1..1_000_000).last(1000) + - (1..1_000_000).last(10000) diff --git a/benchmark/range_min.yml b/benchmark/range_min.yml new file mode 100644 index 0000000000..9e60dd7308 --- /dev/null +++ b/benchmark/range_min.yml @@ -0,0 +1,2 @@ +benchmark: + - (1..10).min diff --git a/benchmark/realpath.yml b/benchmark/realpath.yml new file mode 100644 index 0000000000..90a029d5b9 --- /dev/null +++ b/benchmark/realpath.yml @@ -0,0 +1,30 @@ +prelude: | + f = File + pwd = Dir.pwd + Dir.mkdir('b') unless f.directory?('b') + f.write('b/a', '') unless f.file?('b/a') + + relative = 'b/a' + absolute = File.join(pwd, relative) + dir = 'b' + file = 'a' + + relative_dir = 'b/c' + absolute_dir = File.join(pwd, relative_dir) + file_dir = 'c' +benchmark: + relative_nil: "f.realpath(relative, nil)" + absolute_nil: "f.realpath(absolute, nil)" + relative_relative: "f.realpath(file, dir)" + absolute_relative: "f.realpath(absolute, dir)" + relative_absolute: "f.realpath(relative, pwd)" + relative_nil_dir: "f.realdirpath(relative_dir, nil)" + absolute_nil_dir: "f.realdirpath(absolute_dir, nil)" + relative_relative_dir: "f.realdirpath(file_dir, dir)" + absolute_relative_dir: "f.realdirpath(absolute_dir, dir)" + relative_absolute_dir: "f.realdirpath(relative_dir, pwd)" + relative_nil_notexist: "f.realpath(relative_dir, nil) rescue nil" + absolute_nil_notexist: "f.realpath(absolute_dir, nil) rescue nil" + relative_relative_notexist: "f.realpath(file_dir, dir) rescue nil" + absolute_relative_notexist: "f.realpath(absolute_dir, dir) rescue nil" + relative_absolute_notexist: "f.realpath(relative_dir, pwd) rescue nil" diff --git a/benchmark/require.yml b/benchmark/require.yml index 711d8e11e9..09f218cf08 100644 --- a/benchmark/require.yml +++ b/benchmark/require.yml @@ -15,11 +15,7 @@ prelude: | FileUtils.mkdir_p(data_dir) 1.upto(num_files) do |i| - f = File.open("#{data_dir}/c#{i}.rb", "w") - f.puts <<-END - class C#{i} - end - END + File.write("#{data_dir}/c#{i}.rb", "class C#{i}\n""end\n") end end diff --git a/benchmark/require_thread.yml b/benchmark/require_thread.yml index 87e0ba888b..0c63257106 100644 --- a/benchmark/require_thread.yml +++ b/benchmark/require_thread.yml @@ -15,11 +15,7 @@ prelude: | FileUtils.mkdir_p(data_dir) 1.upto(num_files) do |i| - f = File.open("#{data_dir}/c#{i}.rb", "w") - f.puts <<-END - class C#{i} - end - END + File.write("#{data_dir}/c#{i}.rb", "class C#{i}\n""end\n") end end diff --git a/benchmark/so_ackermann.rb b/benchmark/so_ackermann.rb index 7db5be9050..4effa1ecaf 100644 --- a/benchmark/so_ackermann.rb +++ b/benchmark/so_ackermann.rb @@ -1,5 +1,5 @@ #!/usr/bin/ruby -# -*- mode: ruby -*- +# -*- Ruby -*- # $Id: ackermann-ruby.code,v 1.4 2004/11/13 07:40:41 bfulgham Exp $ # http://www.bagley.org/~doug/shootout/ diff --git a/benchmark/so_array.rb b/benchmark/so_array.rb index 2b8fce8f99..767e03db5f 100644 --- a/benchmark/so_array.rb +++ b/benchmark/so_array.rb @@ -1,5 +1,5 @@ #!/usr/bin/ruby -# -*- mode: ruby -*- +# -*- Ruby -*- # $Id: ary-ruby.code,v 1.4 2004/11/13 07:41:27 bfulgham Exp $ # http://www.bagley.org/~doug/shootout/ # with help from Paul Brannan and Mark Hubbart diff --git a/benchmark/so_concatenate.rb b/benchmark/so_concatenate.rb index 873214de7c..4468e20ac8 100644 --- a/benchmark/so_concatenate.rb +++ b/benchmark/so_concatenate.rb @@ -1,5 +1,5 @@ #!/usr/bin/ruby -# -*- mode: ruby -*- +# -*- Ruby -*- # $Id: strcat-ruby.code,v 1.4 2004/11/13 07:43:28 bfulgham Exp $ # http://www.bagley.org/~doug/shootout/ # based on code from Aristarkh A Zagorodnikov and Dat Nguyen diff --git a/benchmark/so_count_words.yml b/benchmark/so_count_words.yml index d0a6c8dd3e..99683505f9 100644 --- a/benchmark/so_count_words.yml +++ b/benchmark/so_count_words.yml @@ -1,6 +1,5 @@ prelude: | #!/usr/bin/ruby - # -*- mode: ruby -*- wc_input_base = <<EOS Subject: Re: Who was Izchak Miller? diff --git a/benchmark/so_exception.rb b/benchmark/so_exception.rb index deb003a594..eb205b4df1 100644 --- a/benchmark/so_exception.rb +++ b/benchmark/so_exception.rb @@ -1,5 +1,5 @@ #!/usr/bin/ruby -# -*- mode: ruby -*- +# -*- Ruby -*- # $Id: except-ruby.code,v 1.4 2004/11/13 07:41:33 bfulgham Exp $ # http://www.bagley.org/~doug/shootout/ diff --git a/benchmark/so_matrix.rb b/benchmark/so_matrix.rb index e2c5c8e559..2d1e72bda9 100644 --- a/benchmark/so_matrix.rb +++ b/benchmark/so_matrix.rb @@ -1,5 +1,5 @@ #!/usr/bin/ruby -# -*- mode: ruby -*- +# -*- Ruby -*- # $Id: matrix-ruby.code,v 1.4 2004/11/13 07:42:14 bfulgham Exp $ # http://www.bagley.org/~doug/shootout/ diff --git a/benchmark/so_nbody.rb b/benchmark/so_nbody.rb index d6c5bb9e61..9884fc4edc 100644 --- a/benchmark/so_nbody.rb +++ b/benchmark/so_nbody.rb @@ -12,38 +12,38 @@ def _puts *args end class Planet - attr_accessor :x, :y, :z, :vx, :vy, :vz, :mass + attr_accessor :x, :y, :z, :vx, :vy, :vz, :mass - def initialize(x, y, z, vx, vy, vz, mass) - @x, @y, @z = x, y, z - @vx, @vy, @vz = vx * DAYS_PER_YEAR, vy * DAYS_PER_YEAR, vz * DAYS_PER_YEAR - @mass = mass * SOLAR_MASS - end - - def move_from_i(bodies, nbodies, dt, i) - while i < nbodies - b2 = bodies[i] - dx = @x - b2.x - dy = @y - b2.y - dz = @z - b2.z - - distance = Math.sqrt(dx * dx + dy * dy + dz * dz) - mag = dt / (distance * distance * distance) - b_mass_mag, b2_mass_mag = @mass * mag, b2.mass * mag - - @vx -= dx * b2_mass_mag - @vy -= dy * b2_mass_mag - @vz -= dz * b2_mass_mag - b2.vx += dx * b_mass_mag - b2.vy += dy * b_mass_mag - b2.vz += dz * b_mass_mag - i += 1 + def initialize(x, y, z, vx, vy, vz, mass) + @x, @y, @z = x, y, z + @vx, @vy, @vz = vx * DAYS_PER_YEAR, vy * DAYS_PER_YEAR, vz * DAYS_PER_YEAR + @mass = mass * SOLAR_MASS end - @x += dt * @vx - @y += dt * @vy - @z += dt * @vz - end + def move_from_i(bodies, nbodies, dt, i) + while i < nbodies + b2 = bodies[i] + dx = @x - b2.x + dy = @y - b2.y + dz = @z - b2.z + + distance = Math.sqrt(dx * dx + dy * dy + dz * dz) + mag = dt / (distance * distance * distance) + b_mass_mag, b2_mass_mag = @mass * mag, b2.mass * mag + + @vx -= dx * b2_mass_mag + @vy -= dy * b2_mass_mag + @vz -= dz * b2_mass_mag + b2.vx += dx * b_mass_mag + b2.vy += dy * b_mass_mag + b2.vz += dz * b_mass_mag + i += 1 + end + + @x += dt * @vx + @y += dt * @vy + @z += dt * @vz + end end def energy(bodies) diff --git a/benchmark/so_nested_loop.rb b/benchmark/so_nested_loop.rb index a0513f8c47..766fcf7b84 100644 --- a/benchmark/so_nested_loop.rb +++ b/benchmark/so_nested_loop.rb @@ -1,5 +1,5 @@ #!/usr/bin/ruby -# -*- mode: ruby -*- +# -*- Ruby -*- # $Id: nestedloop-ruby.code,v 1.4 2004/11/13 07:42:22 bfulgham Exp $ # http://www.bagley.org/~doug/shootout/ # from Avi Bryant diff --git a/benchmark/so_object.rb b/benchmark/so_object.rb index e8607c7199..131f44624c 100644 --- a/benchmark/so_object.rb +++ b/benchmark/so_object.rb @@ -1,5 +1,5 @@ #!/usr/bin/ruby -# -*- mode: ruby -*- +# -*- Ruby -*- # $Id: objinst-ruby.code,v 1.4 2004/11/13 07:42:25 bfulgham Exp $ # http://www.bagley.org/~doug/shootout/ # with help from Aristarkh Zagorodnikov diff --git a/benchmark/string_capitalize.yml b/benchmark/string_capitalize.yml new file mode 100644 index 0000000000..7d23fd3d35 --- /dev/null +++ b/benchmark/string_capitalize.yml @@ -0,0 +1,10 @@ +prelude: | + str1 = [*"a".."m",*"N".."Z",*"0".."9"].join("") + str10 = str1 * 10 + str100 = str10 * 10 + str1000 = str100 * 10 +benchmark: + capitalize-1: str1.capitalize + capitalize-10: str10.capitalize + capitalize-100: str100.capitalize + capitalize-1000: str1000.capitalize diff --git a/benchmark/string_casecmp.yml b/benchmark/string_casecmp.yml new file mode 100644 index 0000000000..2354040a04 --- /dev/null +++ b/benchmark/string_casecmp.yml @@ -0,0 +1,26 @@ +prelude: | + lstr1 = [*"a".."z",*"0".."9"].join("") + lstr10 = lstr1 * 10 + lstr100 = lstr10 * 10 + lstr1000 = lstr100 * 10 + lnonascii1 = [*"\u{e0}".."\u{ff}"].join("") + lnonascii10 = lnonascii1 * 10 + lnonascii100 = lnonascii10 * 10 + lnonascii1000 = lnonascii100 * 10 + ustr1 = [*"A".."Z",*"0".."9"].join("") + ustr10 = ustr1 * 10 + ustr100 = ustr10 * 10 + ustr1000 = ustr100 * 10 + unonascii1 = [*"\u{c0}".."\u{df}"].join("") + unonascii10 = unonascii1 * 10 + unonascii100 = unonascii10 * 10 + unonascii1000 = unonascii100 * 10 +benchmark: + casecmp-1: lstr1.casecmp(ustr1) + casecmp-10: lstr10.casecmp(ustr10) + casecmp-100: lstr100.casecmp(ustr100) + casecmp-1000: lstr1000.casecmp(ustr1000) + casecmp-nonascii1: lnonascii1.casecmp(unonascii1) + casecmp-nonascii10: lnonascii10.casecmp(unonascii10) + casecmp-nonascii100: lnonascii100.casecmp(unonascii100) + casecmp-nonascii1000: lnonascii1000.casecmp(unonascii1000) diff --git a/benchmark/string_casecmp_p.yml b/benchmark/string_casecmp_p.yml new file mode 100644 index 0000000000..a790ce7d55 --- /dev/null +++ b/benchmark/string_casecmp_p.yml @@ -0,0 +1,26 @@ +prelude: | + lstr1 = [*"a".."z",*"0".."9"].join("") + lstr10 = lstr1 * 10 + lstr100 = lstr10 * 10 + lstr1000 = lstr100 * 10 + lnonascii1 = [*"\u{e0}".."\u{ff}"].join("") + lnonascii10 = lnonascii1 * 10 + lnonascii100 = lnonascii10 * 10 + lnonascii1000 = lnonascii100 * 10 + ustr1 = [*"A".."Z",*"0".."9"].join("") + ustr10 = ustr1 * 10 + ustr100 = ustr10 * 10 + ustr1000 = ustr100 * 10 + unonascii1 = [*"\u{c0}".."\u{df}"].join("") + unonascii10 = unonascii1 * 10 + unonascii100 = unonascii10 * 10 + unonascii1000 = unonascii100 * 10 +benchmark: + casecmp_p-1: lstr1.casecmp?(ustr1) + casecmp_p-10: lstr10.casecmp?(ustr10) + casecmp_p-100: lstr100.casecmp?(ustr100) + casecmp_p-1000: lstr1000.casecmp?(ustr1000) + casecmp_p-nonascii1: lnonascii1.casecmp?(unonascii1) + casecmp_p-nonascii10: lnonascii10.casecmp?(unonascii10) + casecmp_p-nonascii100: lnonascii100.casecmp?(unonascii100) + casecmp_p-nonascii1000: lnonascii1000.casecmp?(unonascii1000) diff --git a/benchmark/string_concat.yml b/benchmark/string_concat.yml new file mode 100644 index 0000000000..e65c00cca9 --- /dev/null +++ b/benchmark/string_concat.yml @@ -0,0 +1,45 @@ +prelude: | + CHUNK = "a" * 64 + UCHUNK = "é" * 32 + GC.disable # GC causes a lot of variance +benchmark: + binary_concat_7bit: | + buffer = String.new(capacity: 4096, encoding: Encoding::BINARY) + buffer << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK + buffer << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK + buffer << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK + buffer << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK + buffer << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK + buffer << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK + buffer << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK + buffer << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK + utf8_concat_7bit: | + buffer = String.new(capacity: 4096, encoding: Encoding::UTF_8) + buffer << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK + buffer << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK + buffer << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK + buffer << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK + buffer << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK + buffer << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK + buffer << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK + buffer << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK << CHUNK + utf8_concat_UTF8: | + buffer = String.new(capacity: 4096, encoding: Encoding::UTF_8) + buffer << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK + buffer << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK + buffer << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK + buffer << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK + buffer << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK + buffer << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK + buffer << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK + buffer << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK << UCHUNK + interpolation: | + buffer = "#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}" \ + "#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}" \ + "#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}" \ + "#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}" \ + "#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}" \ + "#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}" \ + "#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}" \ + "#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}" \ + "#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}#{CHUNK}" diff --git a/benchmark/string_downcase.yml b/benchmark/string_downcase.yml new file mode 100644 index 0000000000..1fea6afbec --- /dev/null +++ b/benchmark/string_downcase.yml @@ -0,0 +1,18 @@ +prelude: | + str1 = [*"A".."Z",*"0".."9"].join("") + str10 = str1 * 10 + str100 = str10 * 10 + str1000 = str100 * 10 + nonascii1 = [*"\u{c0}".."\u{df}"].join("") + nonascii10 = nonascii1 * 10 + nonascii100 = nonascii10 * 10 + nonascii1000 = nonascii100 * 10 +benchmark: + downcase-1: str1.upcase + downcase-10: str10.upcase + downcase-100: str100.upcase + downcase-1000: str1000.upcase + downcase-nonascii1: nonascii1.downcase + downcase-nonascii10: nonascii10.downcase + downcase-nonascii100: nonascii100.downcase + downcase-nonascii1000: nonascii1000.downcase diff --git a/benchmark/string_slice.yml b/benchmark/string_slice.yml new file mode 100644 index 0000000000..fc2393c5d1 --- /dev/null +++ b/benchmark/string_slice.yml @@ -0,0 +1,11 @@ +prelude: | + long_string = "x"*1000+"-hÃ¥r" +benchmark: + regexp-short: | + "x-hÃ¥r".slice!(/-(.)(.)(.)/, 3) + regexp-long: | + long_string.dup.slice!(/-(.)(.)(.)/, 3) + string-short: | + "x-hÃ¥r".slice!("r") + string-long: | + long_string.dup.slice!("r") diff --git a/benchmark/string_split.yml b/benchmark/string_split.yml new file mode 100644 index 0000000000..cc2c7d7855 --- /dev/null +++ b/benchmark/string_split.yml @@ -0,0 +1,22 @@ +prelude: | + str1 = [*0..5].join(" ") + " " + str10 = str1 * 10 + str100 = str10 * 10 + str1000 = str100 * 10 +benchmark: + to_chars-1: str1.split('') + to_chars-10: str10.split('') + to_chars-100: str100.split('') + to_chars-1000: str1000.split('') + to_words-1: str1.split(' ') + to_words-10: str10.split(' ') + to_words-100: str100.split(' ') + to_words-1000: str1000.split(' ') + re_chars-1: str1.split(//) + re_chars-10: str10.split(//) + re_chars-100: str100.split(//) + re_chars-1000: str1000.split(//) + re_space-1: str1.split(/ /) + re_space-10: str10.split(/ /) + re_space-100: str100.split(/ /) + re_space-1000: str1000.split(/ /) diff --git a/benchmark/string_swapcase.yml b/benchmark/string_swapcase.yml new file mode 100644 index 0000000000..eeb5928907 --- /dev/null +++ b/benchmark/string_swapcase.yml @@ -0,0 +1,18 @@ +prelude: | + str1 = [*"A".."M",*"n".."z",*"0".."9"].join("") + str10 = str1 * 10 + str100 = str10 * 10 + str1000 = str100 * 10 + nonascii1 = [*"\u{c0}".."\u{cf}",*"\u{f0}".."\u{ff}"].join("") + nonascii10 = nonascii1 * 10 + nonascii100 = nonascii10 * 10 + nonascii1000 = nonascii100 * 10 +benchmark: + swapcase-1: str1.swapcase + swapcase-10: str10.swapcase + swapcase-100: str100.swapcase + swapcase-1000: str1000.swapcase + swapcase-nonascii1: nonascii1.swapcase + swapcase-nonascii10: nonascii10.swapcase + swapcase-nonascii100: nonascii100.swapcase + swapcase-nonascii1000: nonascii1000.swapcase diff --git a/benchmark/string_upcase.yml b/benchmark/string_upcase.yml new file mode 100644 index 0000000000..dab84bbde2 --- /dev/null +++ b/benchmark/string_upcase.yml @@ -0,0 +1,18 @@ +prelude: | + str1 = [*"a".."z",*"0".."9"].join("") + str10 = str1 * 10 + str100 = str10 * 10 + str1000 = str100 * 10 + nonascii1 = [*"\u{e0}".."\u{ff}"].join("") + nonascii10 = nonascii1 * 10 + nonascii100 = nonascii10 * 10 + nonascii1000 = nonascii100 * 10 +benchmark: + upcase-1: str1.upcase + upcase-10: str10.upcase + upcase-100: str100.upcase + upcase-1000: str1000.upcase + upcase-nonascii1: nonascii1.upcase + upcase-nonascii10: nonascii10.upcase + upcase-nonascii100: nonascii100.upcase + upcase-nonascii1000: nonascii1000.upcase diff --git a/benchmark/time_at.yml b/benchmark/time_at.yml new file mode 100644 index 0000000000..3247efbe77 --- /dev/null +++ b/benchmark/time_at.yml @@ -0,0 +1,7 @@ +prelude: | + # frozen_string_literal: true +benchmark: + - 'Time.at(0)' + - 'Time.at(0, 500)' + - 'Time.at(0, in: "+09:00")' + - 'Time.at(0, 500, in: "+09:00")' diff --git a/benchmark/time_new.yml b/benchmark/time_new.yml new file mode 100644 index 0000000000..5947dd3a41 --- /dev/null +++ b/benchmark/time_new.yml @@ -0,0 +1,4 @@ +benchmark: + - 'Time.new(2021)' + - 'Time.new(2021, 8, 22)' + - 'Time.new(2021, 8, 22, in: "+09:00")' diff --git a/benchmark/time_now.yml b/benchmark/time_now.yml new file mode 100644 index 0000000000..f6d6a31489 --- /dev/null +++ b/benchmark/time_now.yml @@ -0,0 +1,3 @@ +benchmark: + - 'Time.now' + - 'Time.now(in: "+09:00")' diff --git a/benchmark/time_parse.yml b/benchmark/time_parse.yml new file mode 100644 index 0000000000..6060b58bc6 --- /dev/null +++ b/benchmark/time_parse.yml @@ -0,0 +1,10 @@ +prelude: | + require 'time' + inspect = "2021-08-23 09:57:02 +0900" + iso8601 = "2021-08-23T09:57:02+09:00" +benchmark: + - Time.iso8601(iso8601) + - Time.parse(iso8601) + - Time.parse(inspect) + - Time.new(iso8601) rescue Time.iso8601(iso8601) + - Time.new(inspect) rescue Time.parse(inspect) diff --git a/benchmark/time_strptime.yml b/benchmark/time_strptime.yml new file mode 100644 index 0000000000..8d89ebb7a7 --- /dev/null +++ b/benchmark/time_strptime.yml @@ -0,0 +1,13 @@ +prelude: | + require 'time' +benchmark: + - Time.strptime("28/Aug/2005:06:54:20 +0000", "%d/%b/%Y:%T %z") + - Time.strptime("1", "%s") + - Time.strptime("0 +0100", "%s %z") + - Time.strptime("0 UTC", "%s %z") + - Time.strptime("1.5", "%s.%N") + - Time.strptime("1.000000000001", "%s.%N") + - Time.strptime("20010203 -0200", "%Y%m%d %z") + - Time.strptime("20010203 UTC", "%Y%m%d %z") + - Time.strptime("2018-365", "%Y-%j") + - Time.strptime("2018-091", "%Y-%j") diff --git a/benchmark/vm1_attr_ivar.yml b/benchmark/vm1_attr_ivar.yml deleted file mode 100644 index f714dd9bd9..0000000000 --- a/benchmark/vm1_attr_ivar.yml +++ /dev/null @@ -1,14 +0,0 @@ -prelude: | - class C - attr_reader :a, :b - def initialize - @a = nil - @b = nil - end - end - obj = C.new -benchmark: - vm1_attr_ivar: | - j = obj.a - k = obj.b -loop_count: 30000000 diff --git a/benchmark/vm1_attr_ivar_set.yml b/benchmark/vm1_attr_ivar_set.yml deleted file mode 100644 index f383e59ef4..0000000000 --- a/benchmark/vm1_attr_ivar_set.yml +++ /dev/null @@ -1,14 +0,0 @@ -prelude: | - class C - attr_accessor :a, :b - def initialize - @a = nil - @b = nil - end - end - obj = C.new -benchmark: - vm1_attr_ivar_set: | - obj.a = 1 - obj.b = 2 -loop_count: 30000000 diff --git a/benchmark/vm1_block.yml b/benchmark/vm1_block.yml deleted file mode 100644 index ac7c940f93..0000000000 --- a/benchmark/vm1_block.yml +++ /dev/null @@ -1,9 +0,0 @@ -prelude: | - def m - yield - end -benchmark: - vm1_block: | - m{ - } -loop_count: 30000000 diff --git a/benchmark/vm1_blockparam.yml b/benchmark/vm1_blockparam.yml deleted file mode 100644 index 947b8c53d5..0000000000 --- a/benchmark/vm1_blockparam.yml +++ /dev/null @@ -1,7 +0,0 @@ -prelude: | - def m &b - end -benchmark: - vm1_blockparam: | - m{} -loop_count: 30000000 diff --git a/benchmark/vm1_blockparam_call.yml b/benchmark/vm1_blockparam_call.yml deleted file mode 100644 index e2817a3ce2..0000000000 --- a/benchmark/vm1_blockparam_call.yml +++ /dev/null @@ -1,8 +0,0 @@ -prelude: | - def m &b - b.call - end -benchmark: - vm1_blockparam_call: | - m{} -loop_count: 30000000 diff --git a/benchmark/vm1_blockparam_pass.yml b/benchmark/vm1_blockparam_pass.yml deleted file mode 100644 index ca1bef3369..0000000000 --- a/benchmark/vm1_blockparam_pass.yml +++ /dev/null @@ -1,12 +0,0 @@ -prelude: | - def bp_yield - yield - end - - def bp_pass &b - bp_yield &b - end -benchmark: - vm1_blockparam_pass: | - bp_pass{} -loop_count: 30000000 diff --git a/benchmark/vm1_blockparam_yield.yml b/benchmark/vm1_blockparam_yield.yml deleted file mode 100644 index 56ae617798..0000000000 --- a/benchmark/vm1_blockparam_yield.yml +++ /dev/null @@ -1,8 +0,0 @@ -prelude: | - def bp_yield &b - yield - end -benchmark: - vm1_blockparam_yield: | - bp_yield{} -loop_count: 30000000 diff --git a/benchmark/vm1_const.yml b/benchmark/vm1_const.yml deleted file mode 100644 index b98db1545c..0000000000 --- a/benchmark/vm1_const.yml +++ /dev/null @@ -1,7 +0,0 @@ -prelude: | - Const = 1 -benchmark: - vm1_const: | - j = Const - k = Const -loop_count: 30000000 diff --git a/benchmark/vm1_ensure.yml b/benchmark/vm1_ensure.yml deleted file mode 100644 index afbbe38bec..0000000000 --- a/benchmark/vm1_ensure.yml +++ /dev/null @@ -1,14 +0,0 @@ -# Not utilizing loop_count since using it for this is too unstable for now -benchmark: - vm1_ensure: | - i = 0 - while i<30_000_000 - i += 1 - begin - begin - ensure - end - ensure - end - end -loop_count: 1 diff --git a/benchmark/vm1_float_simple.yml b/benchmark/vm1_float_simple.yml deleted file mode 100644 index 4e9ad1852b..0000000000 --- a/benchmark/vm1_float_simple.yml +++ /dev/null @@ -1,8 +0,0 @@ -prelude: | - f = 0.0 -benchmark: - vm1_float_simple: | - f += 0.1; f -= 0.1 - f += 0.1; f -= 0.1 - f += 0.1; f -= 0.1 -loop_count: 30000000 diff --git a/benchmark/vm1_gc_short_lived.yml b/benchmark/vm1_gc_short_lived.yml deleted file mode 100644 index 8fdcb7371d..0000000000 --- a/benchmark/vm1_gc_short_lived.yml +++ /dev/null @@ -1,9 +0,0 @@ -benchmark: - vm1_gc_short_lived: | - a = '' # short-lived String - b = '' - c = '' - d = '' - e = '' - f = '' -loop_count: 30000000 diff --git a/benchmark/vm1_gc_short_with_complex_long.yml b/benchmark/vm1_gc_short_with_complex_long.yml deleted file mode 100644 index c22ea74a60..0000000000 --- a/benchmark/vm1_gc_short_with_complex_long.yml +++ /dev/null @@ -1,25 +0,0 @@ -prelude: | - def nested_hash h, n - if n == 0 - '' - else - 10.times{ - h[Object.new] = nested_hash(h, n-1) - } - end - end - - long_lived = Hash.new - nested_hash long_lived, 6 - - GC.start - GC.start -benchmark: - vm1_gc_short_with_complex_long: | - a = '' # short-lived String - b = '' - c = '' - d = '' - e = '' - f = '' -loop_count: 30000000 diff --git a/benchmark/vm1_gc_short_with_long.yml b/benchmark/vm1_gc_short_with_long.yml deleted file mode 100644 index c731aae548..0000000000 --- a/benchmark/vm1_gc_short_with_long.yml +++ /dev/null @@ -1,13 +0,0 @@ -prelude: | - long_lived = Array.new(1_000_000){|i| "#{i}"} - GC.start - GC.start -benchmark: - vm1_gc_short_with_long: | - a = '' # short-lived String - b = '' - c = '' - d = '' - e = '' - f = '' -loop_count: 30000000 diff --git a/benchmark/vm1_gc_short_with_symbol.yml b/benchmark/vm1_gc_short_with_symbol.yml deleted file mode 100644 index 7fc1abedd8..0000000000 --- a/benchmark/vm1_gc_short_with_symbol.yml +++ /dev/null @@ -1,13 +0,0 @@ -prelude: | - 50_000.times{|i| sym = "sym#{i}".to_sym} - GC.start - GC.start -benchmark: - vm1_gc_short_with_symbol: | - a = '' # short-lived String - b = '' - c = '' - d = '' - e = '' - f = '' -loop_count: 30000000 diff --git a/benchmark/vm1_gc_wb_ary.yml b/benchmark/vm1_gc_wb_ary.yml deleted file mode 100644 index 50fb4b6f84..0000000000 --- a/benchmark/vm1_gc_wb_ary.yml +++ /dev/null @@ -1,12 +0,0 @@ -prelude: | - short_lived_ary = [] - - if RUBY_VERSION >= "2.2.0" - GC.start(full_mark: false, immediate_mark: true, immediate_sweep: true) - end - - short_lived = '' -benchmark: - vm1_gc_wb_ary: | - short_lived_ary[0] = short_lived # write barrier -loop_count: 30000000 diff --git a/benchmark/vm1_gc_wb_ary_promoted.yml b/benchmark/vm1_gc_wb_ary_promoted.yml deleted file mode 100644 index cf9b5de005..0000000000 --- a/benchmark/vm1_gc_wb_ary_promoted.yml +++ /dev/null @@ -1,15 +0,0 @@ -prelude: | - long_lived = [] - - if RUBY_VERSION > "2.2.0" - 3.times{ GC.start(full_mark: false, immediate_mark: true, immediate_sweep: true) } - elsif - GC.start - end - - short_lived = '' - -benchmark: - vm1_gc_wb_ary_promoted: | - long_lived[0] = short_lived # write barrier -loop_count: 30000000 diff --git a/benchmark/vm1_gc_wb_obj.yml b/benchmark/vm1_gc_wb_obj.yml deleted file mode 100644 index 9dc08e7e1a..0000000000 --- a/benchmark/vm1_gc_wb_obj.yml +++ /dev/null @@ -1,15 +0,0 @@ -prelude: | - class C - attr_accessor :foo - end - short_lived_obj = C.new - - if RUBY_VERSION >= "2.2.0" - GC.start(full_mark: false, immediate_mark: true, immediate_sweep: true) - end - - short_lived = '' -benchmark: - vm1_gc_wb_obj: | - short_lived_obj.foo = short_lived # write barrier -loop_count: 30000000 diff --git a/benchmark/vm1_gc_wb_obj_promoted.yml b/benchmark/vm1_gc_wb_obj_promoted.yml deleted file mode 100644 index 26859d2a52..0000000000 --- a/benchmark/vm1_gc_wb_obj_promoted.yml +++ /dev/null @@ -1,17 +0,0 @@ -prelude: | - class C - attr_accessor :foo - end - long_lived = C.new - - if RUBY_VERSION >= "2.2.0" - 3.times{ GC.start(full_mark: false, immediate_mark: true, immediate_sweep: true) } - elsif - GC.start - end - - short_lived = '' -benchmark: - vm1_gc_wb_obj_promoted: | - long_lived.foo = short_lived # write barrier -loop_count: 30000000 diff --git a/benchmark/vm1_ivar.yml b/benchmark/vm1_ivar.yml deleted file mode 100644 index 7aa6fac729..0000000000 --- a/benchmark/vm1_ivar.yml +++ /dev/null @@ -1,6 +0,0 @@ -prelude: "@a = 1\n" -benchmark: - vm1_ivar: | - j = @a - k = @a -loop_count: 30000000 diff --git a/benchmark/vm1_ivar_set.yml b/benchmark/vm1_ivar_set.yml deleted file mode 100644 index 6f19412d16..0000000000 --- a/benchmark/vm1_ivar_set.yml +++ /dev/null @@ -1,5 +0,0 @@ -benchmark: - vm1_ivar_set: | - @a = 1 - @b = 2 -loop_count: 30000000 diff --git a/benchmark/vm1_length.yml b/benchmark/vm1_length.yml deleted file mode 100644 index a18e2ca2e6..0000000000 --- a/benchmark/vm1_length.yml +++ /dev/null @@ -1,8 +0,0 @@ -prelude: | - a = 'abc' - b = [1, 2, 3] -benchmark: - vm1_length: | - a.length - b.length -loop_count: 30000000 diff --git a/benchmark/vm1_lvar_init.yml b/benchmark/vm1_lvar_init.yml deleted file mode 100644 index 10e2becef9..0000000000 --- a/benchmark/vm1_lvar_init.yml +++ /dev/null @@ -1,21 +0,0 @@ -# while loop cost is not removed because `i` is used in the script -benchmark: - vm1_lvar_init: | - def m v - unless v - # unreachable code - v1 = v2 = v3 = v4 = v5 = v6 = v7 = v8 = v9 = v10 = - v11 = v12 = v13 = v14 = v15 = v16 = v17 = v18 = v19 = v20 = - v21 = v22 = v23 = v24 = v25 = v26 = v27 = v28 = v29 = v30 = - v31 = v32 = v33 = v34 = v35 = v36 = v37 = v38 = v39 = v40 = - v41 = v42 = v43 = v44 = v45 = v46 = v47 = v48 = v49 = v50 = 1 - end - end - - i = 0 - - while i<30_000_000 - i += 1 - m i - end -loop_count: 1 diff --git a/benchmark/vm1_lvar_set.yml b/benchmark/vm1_lvar_set.yml deleted file mode 100644 index df8f6b6ea4..0000000000 --- a/benchmark/vm1_lvar_set.yml +++ /dev/null @@ -1,4 +0,0 @@ -benchmark: - vm1_lvar_set: | - a = b = c = d = e = f = g = h = j = k = l = m = n = o = p = q = r = 1 -loop_count: 30000000 diff --git a/benchmark/vm1_neq.yml b/benchmark/vm1_neq.yml deleted file mode 100644 index 65a8128dda..0000000000 --- a/benchmark/vm1_neq.yml +++ /dev/null @@ -1,7 +0,0 @@ -prelude: | - obj1 = Object.new - obj2 = Object.new -benchmark: - vm1_neq: | - obj1 != obj2 -loop_count: 30000000 diff --git a/benchmark/vm1_not.yml b/benchmark/vm1_not.yml deleted file mode 100644 index 0fb7b282a9..0000000000 --- a/benchmark/vm1_not.yml +++ /dev/null @@ -1,6 +0,0 @@ -prelude: | - obj = Object.new -benchmark: - vm1_not: | - !obj -loop_count: 30000000 diff --git a/benchmark/vm1_rescue.yml b/benchmark/vm1_rescue.yml deleted file mode 100644 index a175b823af..0000000000 --- a/benchmark/vm1_rescue.yml +++ /dev/null @@ -1,6 +0,0 @@ -benchmark: - vm1_rescue: | - begin - rescue - end -loop_count: 30000000 diff --git a/benchmark/vm1_simplereturn.yml b/benchmark/vm1_simplereturn.yml deleted file mode 100644 index 3564aac7e2..0000000000 --- a/benchmark/vm1_simplereturn.yml +++ /dev/null @@ -1,7 +0,0 @@ -prelude: | - def m - return 1 - end -benchmark: - vm1_simplereturn: m -loop_count: 30000000 diff --git a/benchmark/vm1_swap.yml b/benchmark/vm1_swap.yml deleted file mode 100644 index fed87ccd62..0000000000 --- a/benchmark/vm1_swap.yml +++ /dev/null @@ -1,7 +0,0 @@ -prelude: | - a = 1 - b = 2 -benchmark: - vm1_swap: | - a, b = b, a -loop_count: 30000000 diff --git a/benchmark/vm1_yield.yml b/benchmark/vm1_yield.yml deleted file mode 100644 index ae1f9316f9..0000000000 --- a/benchmark/vm1_yield.yml +++ /dev/null @@ -1,13 +0,0 @@ -# while loop cost is not removed due to benchmark_driver.gem's limitation -benchmark: - vm1_yield: | - def m - i = 0 - while i<30_000_000 - i += 1 - yield - end - end - - m{} -loop_count: 1 diff --git a/benchmark/vm2_array.yml b/benchmark/vm2_array.yml deleted file mode 100644 index 7373098d5e..0000000000 --- a/benchmark/vm2_array.yml +++ /dev/null @@ -1,4 +0,0 @@ -benchmark: - vm2_array: | - a = [1,2,3,4,5,6,7,8,9,10] -loop_count: 6000000 diff --git a/benchmark/vm2_bigarray.yml b/benchmark/vm2_bigarray.yml deleted file mode 100644 index 2ad6da3905..0000000000 --- a/benchmark/vm2_bigarray.yml +++ /dev/null @@ -1,105 +0,0 @@ -benchmark: - vm2_bigarray: | - a = [ - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - 1,2,3,4,5,6,7,8,9,10, - ] -loop_count: 6000000 diff --git a/benchmark/vm2_bighash.yml b/benchmark/vm2_bighash.yml deleted file mode 100644 index e9154e4ba9..0000000000 --- a/benchmark/vm2_bighash.yml +++ /dev/null @@ -1,4 +0,0 @@ -benchmark: - vm2_bighash: | - a = {0=>0, 1=>1, 2=>2, 3=>3, 4=>4, 5=>5, 6=>6, 7=>7, 8=>8, 9=>9, 10=>10, 11=>11, 12=>12, 13=>13, 14=>14, 15=>15, 16=>16, 17=>17, 18=>18, 19=>19, 20=>20, 21=>21, 22=>22, 23=>23, 24=>24, 25=>25, 26=>26, 27=>27, 28=>28, 29=>29, 30=>30, 31=>31, 32=>32, 33=>33, 34=>34, 35=>35, 36=>36, 37=>37, 38=>38, 39=>39, 40=>40, 41=>41, 42=>42, 43=>43, 44=>44, 45=>45, 46=>46, 47=>47, 48=>48, 49=>49, 50=>50, 51=>51, 52=>52, 53=>53, 54=>54, 55=>55, 56=>56, 57=>57, 58=>58, 59=>59, 60=>60, 61=>61, 62=>62, 63=>63, 64=>64, 65=>65, 66=>66, 67=>67, 68=>68, 69=>69, 70=>70, 71=>71, 72=>72, 73=>73, 74=>74, 75=>75, 76=>76, 77=>77, 78=>78, 79=>79, 80=>80, 81=>81, 82=>82, 83=>83, 84=>84, 85=>85, 86=>86, 87=>87, 88=>88, 89=>89, 90=>90, 91=>91, 92=>92, 93=>93, 94=>94, 95=>95, 96=>96, 97=>97, 98=>98, 99=>99, 100=>100, 101=>101, 102=>102, 103=>103, 104=>104, 105=>105, 106=>106, 107=>107, 108=>108, 109=>109, 110=>110, 111=>111, 112=>112, 113=>113, 114=>114, 115=>115, 116=>116, 117=>117, 118=>118, 119=>119, 120=>120, 121=>121, 122=>122, 123=>123, 124=>124, 125=>125, 126=>126, 127=>127, 128=>128, 129=>129, 130=>130, 131=>131, 132=>132, 133=>133, 134=>134, 135=>135, 136=>136, 137=>137, 138=>138, 139=>139, 140=>140, 141=>141, 142=>142, 143=>143, 144=>144, 145=>145, 146=>146, 147=>147, 148=>148, 149=>149, 150=>150, 151=>151, 152=>152, 153=>153, 154=>154, 155=>155, 156=>156, 157=>157, 158=>158, 159=>159, 160=>160, 161=>161, 162=>162, 163=>163, 164=>164, 165=>165, 166=>166, 167=>167, 168=>168, 169=>169, 170=>170, 171=>171, 172=>172, 173=>173, 174=>174, 175=>175, 176=>176, 177=>177, 178=>178, 179=>179, 180=>180, 181=>181, 182=>182, 183=>183, 184=>184, 185=>185, 186=>186, 187=>187, 188=>188, 189=>189, 190=>190, 191=>191, 192=>192, 193=>193, 194=>194, 195=>195, 196=>196, 197=>197, 198=>198, 199=>199, 200=>200, 201=>201, 202=>202, 203=>203, 204=>204, 205=>205, 206=>206, 207=>207, 208=>208, 209=>209, 210=>210, 211=>211, 212=>212, 213=>213, 214=>214, 215=>215, 216=>216, 217=>217, 218=>218, 219=>219, 220=>220, 221=>221, 222=>222, 223=>223, 224=>224, 225=>225, 226=>226, 227=>227, 228=>228, 229=>229, 230=>230, 231=>231, 232=>232, 233=>233, 234=>234, 235=>235, 236=>236, 237=>237, 238=>238, 239=>239, 240=>240, 241=>241, 242=>242, 243=>243, 244=>244, 245=>245, 246=>246, 247=>247, 248=>248, 249=>249, 250=>250, 251=>251, 252=>252, 253=>253, 254=>254, 255=>255, 256=>256, 257=>257, 258=>258, 259=>259, 260=>260, 261=>261, 262=>262, 263=>263, 264=>264, 265=>265, 266=>266, 267=>267, 268=>268, 269=>269, 270=>270, 271=>271, 272=>272, 273=>273, 274=>274, 275=>275, 276=>276, 277=>277, 278=>278, 279=>279, 280=>280, 281=>281, 282=>282, 283=>283, 284=>284, 285=>285, 286=>286, 287=>287, 288=>288, 289=>289, 290=>290, 291=>291, 292=>292, 293=>293, 294=>294, 295=>295, 296=>296, 297=>297, 298=>298, 299=>299, 300=>300, 301=>301, 302=>302, 303=>303, 304=>304, 305=>305, 306=>306, 307=>307, 308=>308, 309=>309, 310=>310, 311=>311, 312=>312, 313=>313, 314=>314, 315=>315, 316=>316, 317=>317, 318=>318, 319=>319, 320=>320, 321=>321, 322=>322, 323=>323, 324=>324, 325=>325, 326=>326, 327=>327, 328=>328, 329=>329, 330=>330, 331=>331, 332=>332, 333=>333, 334=>334, 335=>335, 336=>336, 337=>337, 338=>338, 339=>339, 340=>340, 341=>341, 342=>342, 343=>343, 344=>344, 345=>345, 346=>346, 347=>347, 348=>348, 349=>349, 350=>350, 351=>351, 352=>352, 353=>353, 354=>354, 355=>355, 356=>356, 357=>357, 358=>358, 359=>359, 360=>360, 361=>361, 362=>362, 363=>363, 364=>364, 365=>365, 366=>366, 367=>367, 368=>368, 369=>369, 370=>370, 371=>371, 372=>372, 373=>373, 374=>374, 375=>375, 376=>376, 377=>377, 378=>378, 379=>379, 380=>380, 381=>381, 382=>382, 383=>383, 384=>384, 385=>385, 386=>386, 387=>387, 388=>388, 389=>389, 390=>390, 391=>391, 392=>392, 393=>393, 394=>394, 395=>395, 396=>396, 397=>397, 398=>398, 399=>399, 400=>400, 401=>401, 402=>402, 403=>403, 404=>404, 405=>405, 406=>406, 407=>407, 408=>408, 409=>409, 410=>410, 411=>411, 412=>412, 413=>413, 414=>414, 415=>415, 416=>416, 417=>417, 418=>418, 419=>419, 420=>420, 421=>421, 422=>422, 423=>423, 424=>424, 425=>425, 426=>426, 427=>427, 428=>428, 429=>429, 430=>430, 431=>431, 432=>432, 433=>433, 434=>434, 435=>435, 436=>436, 437=>437, 438=>438, 439=>439, 440=>440, 441=>441, 442=>442, 443=>443, 444=>444, 445=>445, 446=>446, 447=>447, 448=>448, 449=>449, 450=>450, 451=>451, 452=>452, 453=>453, 454=>454, 455=>455, 456=>456, 457=>457, 458=>458, 459=>459, 460=>460, 461=>461, 462=>462, 463=>463, 464=>464, 465=>465, 466=>466, 467=>467, 468=>468, 469=>469, 470=>470, 471=>471, 472=>472, 473=>473, 474=>474, 475=>475, 476=>476, 477=>477, 478=>478, 479=>479, 480=>480, 481=>481, 482=>482, 483=>483, 484=>484, 485=>485, 486=>486, 487=>487, 488=>488, 489=>489, 490=>490, 491=>491, 492=>492, 493=>493, 494=>494, 495=>495, 496=>496, 497=>497, 498=>498, 499=>499, 500=>500,} -loop_count: 60000 diff --git a/benchmark/vm2_case.yml b/benchmark/vm2_case.yml deleted file mode 100644 index 7716783c09..0000000000 --- a/benchmark/vm2_case.yml +++ /dev/null @@ -1,13 +0,0 @@ -benchmark: - vm2_case: | - case :foo - when :bar - raise - when :baz - raise - when :boo - raise - when :foo - # noop - end -loop_count: 6000000 diff --git a/benchmark/vm2_case_lit.yml b/benchmark/vm2_case_lit.yml deleted file mode 100644 index c49b8dfe5e..0000000000 --- a/benchmark/vm2_case_lit.yml +++ /dev/null @@ -1,23 +0,0 @@ -# loop_count is not utilized since `i` is involved in the script -benchmark: - vm2_case_lit: | - i = 0 - @ret = [ "foo", true, false, :sym, 6, nil, 0.1, 0xffffffffffffffff ] - def foo(i) - @ret[i % @ret.size] - end - - while i<6_000_000 - case foo(i) - when "foo" then :foo - when true then true - when false then false - when :sym then :sym - when 6 then :fix - when nil then nil - when 0.1 then :float - when 0xffffffffffffffff then :big - end - i += 1 - end -loop_count: 1 diff --git a/benchmark/vm2_defined_method.yml b/benchmark/vm2_defined_method.yml deleted file mode 100644 index e1b0d55674..0000000000 --- a/benchmark/vm2_defined_method.yml +++ /dev/null @@ -1,8 +0,0 @@ -prelude: | - class Object - define_method(:m){} - end -benchmark: - vm2_defined_method: | - m; m; m; m; m; m; m; m; -loop_count: 6000000 diff --git a/benchmark/vm2_dstr.yml b/benchmark/vm2_dstr.yml deleted file mode 100644 index f8bd6e0133..0000000000 --- a/benchmark/vm2_dstr.yml +++ /dev/null @@ -1,6 +0,0 @@ -prelude: | - x = y = 'z' -benchmark: - vm2_dstr: | - str = "foo#{x}bar#{y}baz" -loop_count: 6000000 diff --git a/benchmark/vm2_eval.yml b/benchmark/vm2_eval.yml deleted file mode 100644 index d506a9c079..0000000000 --- a/benchmark/vm2_eval.yml +++ /dev/null @@ -1,4 +0,0 @@ -benchmark: - vm2_eval: | - eval("1") -loop_count: 6000000 diff --git a/benchmark/vm2_fiber_switch.yml b/benchmark/vm2_fiber_switch.yml deleted file mode 100644 index f3e4c91283..0000000000 --- a/benchmark/vm2_fiber_switch.yml +++ /dev/null @@ -1,9 +0,0 @@ -prelude: | - # based on benchmark for [ruby-core:65518] [Feature #10341] by Knut Franke - fib = Fiber.new do - loop { Fiber.yield } - end -benchmark: - vm2_fiber_switch: | - fib.resume -loop_count: 6000000 diff --git a/benchmark/vm2_freezestring.yml b/benchmark/vm2_freezestring.yml deleted file mode 100644 index b78af91a20..0000000000 --- a/benchmark/vm2_freezestring.yml +++ /dev/null @@ -1,10 +0,0 @@ -prelude: | - class String - def freeze - -self - end - end -benchmark: - vm2_freezestring: | - "tXnL1BP5T1WPXMjuFNLQtallEtRcay1t2lHtJSrlVsDgvunlbtfpr/DGdH0NGYE9".freeze -loop_count: 6000000 diff --git a/benchmark/vm2_method.yml b/benchmark/vm2_method.yml deleted file mode 100644 index cc7b9b28ff..0000000000 --- a/benchmark/vm2_method.yml +++ /dev/null @@ -1,8 +0,0 @@ -prelude: | - def m - nil - end -benchmark: - vm2_method: | - m; m; m; m; m; m; m; m; -loop_count: 6000000 diff --git a/benchmark/vm2_method_missing.yml b/benchmark/vm2_method_missing.yml deleted file mode 100644 index cbfb794b25..0000000000 --- a/benchmark/vm2_method_missing.yml +++ /dev/null @@ -1,11 +0,0 @@ -prelude: | - class C - def method_missing mid - end - end - - obj = C.new -benchmark: - vm2_method_missing: | - obj.m; obj.m; obj.m; obj.m; obj.m; obj.m; obj.m; obj.m; -loop_count: 6000000 diff --git a/benchmark/vm2_method_with_block.yml b/benchmark/vm2_method_with_block.yml deleted file mode 100644 index 6e522adccc..0000000000 --- a/benchmark/vm2_method_with_block.yml +++ /dev/null @@ -1,8 +0,0 @@ -prelude: | - def m - nil - end -benchmark: - vm2_method_with_block: | - m{}; m{}; m{}; m{}; m{}; m{}; m{}; m{}; -loop_count: 6000000 diff --git a/benchmark/vm2_module_ann_const_set.yml b/benchmark/vm2_module_ann_const_set.yml deleted file mode 100644 index b0becd9d3d..0000000000 --- a/benchmark/vm2_module_ann_const_set.yml +++ /dev/null @@ -1,4 +0,0 @@ -benchmark: - vm2_module_ann_const_set: | - Module.new.const_set(:X, Module.new) -loop_count: 6000000 diff --git a/benchmark/vm2_module_const_set.yml b/benchmark/vm2_module_const_set.yml deleted file mode 100644 index 05a640069c..0000000000 --- a/benchmark/vm2_module_const_set.yml +++ /dev/null @@ -1,8 +0,0 @@ -prelude: | - module M - end - $VERBOSE = nil -benchmark: - vm2_module_const_set: | - M.const_set(:X, Module.new) -loop_count: 6000000 diff --git a/benchmark/vm2_mutex.yml b/benchmark/vm2_mutex.yml deleted file mode 100644 index c40a90444a..0000000000 --- a/benchmark/vm2_mutex.yml +++ /dev/null @@ -1,8 +0,0 @@ -prelude: | - require 'thread' - - m = Thread::Mutex.new -benchmark: - vm2_mutex: | - m.synchronize{} -loop_count: 6000000 diff --git a/benchmark/vm2_newlambda.yml b/benchmark/vm2_newlambda.yml deleted file mode 100644 index 93133f9f30..0000000000 --- a/benchmark/vm2_newlambda.yml +++ /dev/null @@ -1,4 +0,0 @@ -benchmark: - vm2_newlambda: | - lambda {} -loop_count: 6000000 diff --git a/benchmark/vm2_poly_method.yml b/benchmark/vm2_poly_method.yml deleted file mode 100644 index 0104bdfb66..0000000000 --- a/benchmark/vm2_poly_method.yml +++ /dev/null @@ -1,24 +0,0 @@ -# loop_count is not utilized since `i` is involved in the script -benchmark: - vm2_poly_method: | - class C1 - def m - 1 - end - end - class C2 - def m - 2 - end - end - - o1 = C1.new - o2 = C2.new - - i = 0 - while i<6_000_000 - o = (i % 2 == 0) ? o1 : o2 - o.m; o.m; o.m; o.m; o.m; o.m; o.m; o.m - i += 1 - end -loop_count: 1 diff --git a/benchmark/vm2_poly_method_ov.yml b/benchmark/vm2_poly_method_ov.yml deleted file mode 100644 index 3748073ba2..0000000000 --- a/benchmark/vm2_poly_method_ov.yml +++ /dev/null @@ -1,24 +0,0 @@ -# loop_count is not utilized since `i` is involved in the script -benchmark: - vm2_poly_method_ov: | - class C1 - def m - 1 - end - end - class C2 - def m - 2 - end - end - - o1 = C1.new - o2 = C2.new - - i = 0 - while i<6_000_000 - o = (i % 2 == 0) ? o1 : o2 - # o.m; o.m; o.m; o.m; o.m; o.m; o.m; o.m - i += 1 - end -loop_count: 1 diff --git a/benchmark/vm2_poly_singleton.yml b/benchmark/vm2_poly_singleton.yml deleted file mode 100644 index e58d7bfb37..0000000000 --- a/benchmark/vm2_poly_singleton.yml +++ /dev/null @@ -1,18 +0,0 @@ -# loop_count is not utilized since `i` is involved in the script -benchmark: - vm2_poly_singleton: | - class C1 - def m; 1; end - end - - o1 = C1.new - o2 = C1.new - o2.singleton_class - - i = 0 - while i<6_000_000 # benchmark loop 2 - o = (i % 2 == 0) ? o1 : o2 - o.m; o.m; o.m; o.m; o.m; o.m; o.m; o.m - i += 1 - end -loop_count: 1 diff --git a/benchmark/vm2_proc.yml b/benchmark/vm2_proc.yml deleted file mode 100644 index 5c36e936d9..0000000000 --- a/benchmark/vm2_proc.yml +++ /dev/null @@ -1,12 +0,0 @@ -prelude: | - def m &b - b - end - - pr = m{ - a = 1 - } -benchmark: - vm2_proc: | - pr.call -loop_count: 6000000 diff --git a/benchmark/vm2_raise1.yml b/benchmark/vm2_raise1.yml deleted file mode 100644 index f6eb308968..0000000000 --- a/benchmark/vm2_raise1.yml +++ /dev/null @@ -1,16 +0,0 @@ -prelude: | - def rec n - if n > 0 - rec n-1 - else - raise - end - end -benchmark: - vm2_raise1: | - begin - rec 1 - rescue - # ignore - end -loop_count: 6000000 diff --git a/benchmark/vm2_raise2.yml b/benchmark/vm2_raise2.yml deleted file mode 100644 index 7d51b1b314..0000000000 --- a/benchmark/vm2_raise2.yml +++ /dev/null @@ -1,16 +0,0 @@ -prelude: | - def rec n - if n > 0 - rec n-1 - else - raise - end - end -benchmark: - vm2_raise2: | - begin - rec 10 - rescue - # ignore - end -loop_count: 6000000 diff --git a/benchmark/vm2_regexp.yml b/benchmark/vm2_regexp.yml deleted file mode 100644 index 0f3968a99b..0000000000 --- a/benchmark/vm2_regexp.yml +++ /dev/null @@ -1,6 +0,0 @@ -prelude: | - str = 'xxxhogexxx' -benchmark: - vm2_regexp: | - /hoge/ =~ str -loop_count: 6000000 diff --git a/benchmark/vm2_send.yml b/benchmark/vm2_send.yml deleted file mode 100644 index 44a12a27d9..0000000000 --- a/benchmark/vm2_send.yml +++ /dev/null @@ -1,11 +0,0 @@ -prelude: | - class C - def m - end - end - - o = C.new -benchmark: - vm2_send: | - o.__send__ :m -loop_count: 6000000 diff --git a/benchmark/vm2_string_literal.yml b/benchmark/vm2_string_literal.yml deleted file mode 100644 index 54b0aec1fe..0000000000 --- a/benchmark/vm2_string_literal.yml +++ /dev/null @@ -1,4 +0,0 @@ -benchmark: - vm2_string_literal: | - x = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -loop_count: 6000000 diff --git a/benchmark/vm2_struct_big_aref_hi.yml b/benchmark/vm2_struct_big_aref_hi.yml deleted file mode 100644 index eed1846d28..0000000000 --- a/benchmark/vm2_struct_big_aref_hi.yml +++ /dev/null @@ -1,7 +0,0 @@ -prelude: | - s = Struct.new(*('a'..'z').map { |x| x.to_sym }) - x = s.new -benchmark: - vm2_struct_big_aref_hi: | - x.z # x[25] -loop_count: 6000000 diff --git a/benchmark/vm2_struct_big_aref_lo.yml b/benchmark/vm2_struct_big_aref_lo.yml deleted file mode 100644 index 0915435b76..0000000000 --- a/benchmark/vm2_struct_big_aref_lo.yml +++ /dev/null @@ -1,7 +0,0 @@ -prelude: | - s = Struct.new(*('a'..'z').map { |x| x.to_sym }) - x = s.new -benchmark: - vm2_struct_big_aref_lo: | - x.k # x[10] -loop_count: 6000000 diff --git a/benchmark/vm2_struct_big_aset.yml b/benchmark/vm2_struct_big_aset.yml deleted file mode 100644 index 6af50103d3..0000000000 --- a/benchmark/vm2_struct_big_aset.yml +++ /dev/null @@ -1,11 +0,0 @@ -# loop_count is not utilized since `i` is involved in the script -benchmark: - vm2_struct_big_aset: | - s = Struct.new(*('a'..'z').map { |x| x.to_sym }) - x = s.new - i = 0 - while i<6_000_000 - i += 1 - x.k = i # x[10] = i - end -loop_count: 1 diff --git a/benchmark/vm2_struct_big_href_hi.yml b/benchmark/vm2_struct_big_href_hi.yml deleted file mode 100644 index 60aa7fddf3..0000000000 --- a/benchmark/vm2_struct_big_href_hi.yml +++ /dev/null @@ -1,7 +0,0 @@ -prelude: | - s = Struct.new(*('a'..'z').map { |x| x.to_sym }) - x = s.new -benchmark: - vm2_struct_big_href_hi: | - x[:z] -loop_count: 6000000 diff --git a/benchmark/vm2_struct_big_href_lo.yml b/benchmark/vm2_struct_big_href_lo.yml deleted file mode 100644 index c55c0bd16c..0000000000 --- a/benchmark/vm2_struct_big_href_lo.yml +++ /dev/null @@ -1,7 +0,0 @@ -prelude: | - s = Struct.new(*('a'..'z').map { |x| x.to_sym }) - x = s.new -benchmark: - vm2_struct_big_href_lo: | - x[:k] -loop_count: 6000000 diff --git a/benchmark/vm2_struct_big_hset.yml b/benchmark/vm2_struct_big_hset.yml deleted file mode 100644 index d199c5bd47..0000000000 --- a/benchmark/vm2_struct_big_hset.yml +++ /dev/null @@ -1,11 +0,0 @@ -# loop_count is not utilized since `i` is involved in the script -benchmark: - vm2_struct_big_hset: | - s = Struct.new(*('a'..'z').map { |x| x.to_sym }) - x = s.new - i = 0 - while i<6_000_000 - i += 1 - x[:k] = i - end -loop_count: 1 diff --git a/benchmark/vm2_struct_small_aref.yml b/benchmark/vm2_struct_small_aref.yml deleted file mode 100644 index 83381bed3a..0000000000 --- a/benchmark/vm2_struct_small_aref.yml +++ /dev/null @@ -1,7 +0,0 @@ -prelude: | - s = Struct.new(:a, :b, :c) - x = s.new -benchmark: - vm2_struct_small_aref: | - x.a -loop_count: 6000000 diff --git a/benchmark/vm2_struct_small_aset.yml b/benchmark/vm2_struct_small_aset.yml deleted file mode 100644 index 3e84a61dd0..0000000000 --- a/benchmark/vm2_struct_small_aset.yml +++ /dev/null @@ -1,11 +0,0 @@ -# loop_count is not utilized since `i` is involved in the script -benchmark: - vm2_struct_small_aset: | - s = Struct.new(:a, :b, :c) - x = s.new - i = 0 - while i<6_000_000 - i += 1 - x.a = i - end -loop_count: 1 diff --git a/benchmark/vm2_struct_small_href.yml b/benchmark/vm2_struct_small_href.yml deleted file mode 100644 index b744f070d1..0000000000 --- a/benchmark/vm2_struct_small_href.yml +++ /dev/null @@ -1,7 +0,0 @@ -prelude: | - s = Struct.new(:a, :b, :c) - x = s.new -benchmark: - vm2_struct_small_href: | - x[:a] -loop_count: 6000000 diff --git a/benchmark/vm2_struct_small_hset.yml b/benchmark/vm2_struct_small_hset.yml deleted file mode 100644 index d43845d6e0..0000000000 --- a/benchmark/vm2_struct_small_hset.yml +++ /dev/null @@ -1,7 +0,0 @@ -prelude: | - s = Struct.new(:a, :b, :c) - x = s.new -benchmark: - vm2_struct_small_hset: | - x[:a] = 1 -loop_count: 6000000 diff --git a/benchmark/vm2_super.yml b/benchmark/vm2_super.yml deleted file mode 100644 index 674743762a..0000000000 --- a/benchmark/vm2_super.yml +++ /dev/null @@ -1,17 +0,0 @@ -prelude: | - class C - def m - 1 - end - end - - class CC < C - def m - super() - end - end - - obj = CC.new -benchmark: - vm2_super: obj.m -loop_count: 6000000 diff --git a/benchmark/vm2_unif1.yml b/benchmark/vm2_unif1.yml deleted file mode 100644 index caef13279f..0000000000 --- a/benchmark/vm2_unif1.yml +++ /dev/null @@ -1,7 +0,0 @@ -prelude: | - def m a, b - end -benchmark: - vm2_unif1: | - m 100, 200 -loop_count: 6000000 diff --git a/benchmark/vm2_zsuper.yml b/benchmark/vm2_zsuper.yml deleted file mode 100644 index f760cfd48e..0000000000 --- a/benchmark/vm2_zsuper.yml +++ /dev/null @@ -1,18 +0,0 @@ -prelude: | - class C - def m a - 1 - end - end - - class CC < C - def m a - super - end - end - - obj = CC.new -benchmark: - vm2_zsuper: | - obj.m 10 -loop_count: 6000000 diff --git a/benchmark/vm_array.yml b/benchmark/vm_array.yml new file mode 100644 index 0000000000..2a177237ef --- /dev/null +++ b/benchmark/vm_array.yml @@ -0,0 +1,4 @@ +benchmark: + vm_array: | + a = [1,2,3,4,5,6,7,8,9,10] +loop_count: 6000000 diff --git a/benchmark/vm_attr_ivar.yml b/benchmark/vm_attr_ivar.yml new file mode 100644 index 0000000000..75b803478e --- /dev/null +++ b/benchmark/vm_attr_ivar.yml @@ -0,0 +1,14 @@ +prelude: | + class C + attr_reader :a, :b + def initialize + @a = nil + @b = nil + end + end + obj = C.new +benchmark: + vm_attr_ivar: | + j = obj.a + k = obj.b +loop_count: 30000000 diff --git a/benchmark/vm_attr_ivar_set.yml b/benchmark/vm_attr_ivar_set.yml new file mode 100644 index 0000000000..a0d379b18a --- /dev/null +++ b/benchmark/vm_attr_ivar_set.yml @@ -0,0 +1,14 @@ +prelude: | + class C + attr_accessor :a, :b + def initialize + @a = nil + @b = nil + end + end + obj = C.new +benchmark: + vm_attr_ivar_set: | + obj.a = 1 + obj.b = 2 +loop_count: 30000000 diff --git a/benchmark/vm3_backtrace.rb b/benchmark/vm_backtrace.rb index 0fbf73e1ca..0fbf73e1ca 100644 --- a/benchmark/vm3_backtrace.rb +++ b/benchmark/vm_backtrace.rb diff --git a/benchmark/vm_bigarray.yml b/benchmark/vm_bigarray.yml new file mode 100644 index 0000000000..8b2d3f3443 --- /dev/null +++ b/benchmark/vm_bigarray.yml @@ -0,0 +1,105 @@ +benchmark: + vm_bigarray: | + a = [ + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + 1,2,3,4,5,6,7,8,9,10, + ] +loop_count: 6000000 diff --git a/benchmark/vm_bighash.yml b/benchmark/vm_bighash.yml new file mode 100644 index 0000000000..4dacfde793 --- /dev/null +++ b/benchmark/vm_bighash.yml @@ -0,0 +1,4 @@ +benchmark: + vm_bighash: | + a = {0=>0, 1=>1, 2=>2, 3=>3, 4=>4, 5=>5, 6=>6, 7=>7, 8=>8, 9=>9, 10=>10, 11=>11, 12=>12, 13=>13, 14=>14, 15=>15, 16=>16, 17=>17, 18=>18, 19=>19, 20=>20, 21=>21, 22=>22, 23=>23, 24=>24, 25=>25, 26=>26, 27=>27, 28=>28, 29=>29, 30=>30, 31=>31, 32=>32, 33=>33, 34=>34, 35=>35, 36=>36, 37=>37, 38=>38, 39=>39, 40=>40, 41=>41, 42=>42, 43=>43, 44=>44, 45=>45, 46=>46, 47=>47, 48=>48, 49=>49, 50=>50, 51=>51, 52=>52, 53=>53, 54=>54, 55=>55, 56=>56, 57=>57, 58=>58, 59=>59, 60=>60, 61=>61, 62=>62, 63=>63, 64=>64, 65=>65, 66=>66, 67=>67, 68=>68, 69=>69, 70=>70, 71=>71, 72=>72, 73=>73, 74=>74, 75=>75, 76=>76, 77=>77, 78=>78, 79=>79, 80=>80, 81=>81, 82=>82, 83=>83, 84=>84, 85=>85, 86=>86, 87=>87, 88=>88, 89=>89, 90=>90, 91=>91, 92=>92, 93=>93, 94=>94, 95=>95, 96=>96, 97=>97, 98=>98, 99=>99, 100=>100, 101=>101, 102=>102, 103=>103, 104=>104, 105=>105, 106=>106, 107=>107, 108=>108, 109=>109, 110=>110, 111=>111, 112=>112, 113=>113, 114=>114, 115=>115, 116=>116, 117=>117, 118=>118, 119=>119, 120=>120, 121=>121, 122=>122, 123=>123, 124=>124, 125=>125, 126=>126, 127=>127, 128=>128, 129=>129, 130=>130, 131=>131, 132=>132, 133=>133, 134=>134, 135=>135, 136=>136, 137=>137, 138=>138, 139=>139, 140=>140, 141=>141, 142=>142, 143=>143, 144=>144, 145=>145, 146=>146, 147=>147, 148=>148, 149=>149, 150=>150, 151=>151, 152=>152, 153=>153, 154=>154, 155=>155, 156=>156, 157=>157, 158=>158, 159=>159, 160=>160, 161=>161, 162=>162, 163=>163, 164=>164, 165=>165, 166=>166, 167=>167, 168=>168, 169=>169, 170=>170, 171=>171, 172=>172, 173=>173, 174=>174, 175=>175, 176=>176, 177=>177, 178=>178, 179=>179, 180=>180, 181=>181, 182=>182, 183=>183, 184=>184, 185=>185, 186=>186, 187=>187, 188=>188, 189=>189, 190=>190, 191=>191, 192=>192, 193=>193, 194=>194, 195=>195, 196=>196, 197=>197, 198=>198, 199=>199, 200=>200, 201=>201, 202=>202, 203=>203, 204=>204, 205=>205, 206=>206, 207=>207, 208=>208, 209=>209, 210=>210, 211=>211, 212=>212, 213=>213, 214=>214, 215=>215, 216=>216, 217=>217, 218=>218, 219=>219, 220=>220, 221=>221, 222=>222, 223=>223, 224=>224, 225=>225, 226=>226, 227=>227, 228=>228, 229=>229, 230=>230, 231=>231, 232=>232, 233=>233, 234=>234, 235=>235, 236=>236, 237=>237, 238=>238, 239=>239, 240=>240, 241=>241, 242=>242, 243=>243, 244=>244, 245=>245, 246=>246, 247=>247, 248=>248, 249=>249, 250=>250, 251=>251, 252=>252, 253=>253, 254=>254, 255=>255, 256=>256, 257=>257, 258=>258, 259=>259, 260=>260, 261=>261, 262=>262, 263=>263, 264=>264, 265=>265, 266=>266, 267=>267, 268=>268, 269=>269, 270=>270, 271=>271, 272=>272, 273=>273, 274=>274, 275=>275, 276=>276, 277=>277, 278=>278, 279=>279, 280=>280, 281=>281, 282=>282, 283=>283, 284=>284, 285=>285, 286=>286, 287=>287, 288=>288, 289=>289, 290=>290, 291=>291, 292=>292, 293=>293, 294=>294, 295=>295, 296=>296, 297=>297, 298=>298, 299=>299, 300=>300, 301=>301, 302=>302, 303=>303, 304=>304, 305=>305, 306=>306, 307=>307, 308=>308, 309=>309, 310=>310, 311=>311, 312=>312, 313=>313, 314=>314, 315=>315, 316=>316, 317=>317, 318=>318, 319=>319, 320=>320, 321=>321, 322=>322, 323=>323, 324=>324, 325=>325, 326=>326, 327=>327, 328=>328, 329=>329, 330=>330, 331=>331, 332=>332, 333=>333, 334=>334, 335=>335, 336=>336, 337=>337, 338=>338, 339=>339, 340=>340, 341=>341, 342=>342, 343=>343, 344=>344, 345=>345, 346=>346, 347=>347, 348=>348, 349=>349, 350=>350, 351=>351, 352=>352, 353=>353, 354=>354, 355=>355, 356=>356, 357=>357, 358=>358, 359=>359, 360=>360, 361=>361, 362=>362, 363=>363, 364=>364, 365=>365, 366=>366, 367=>367, 368=>368, 369=>369, 370=>370, 371=>371, 372=>372, 373=>373, 374=>374, 375=>375, 376=>376, 377=>377, 378=>378, 379=>379, 380=>380, 381=>381, 382=>382, 383=>383, 384=>384, 385=>385, 386=>386, 387=>387, 388=>388, 389=>389, 390=>390, 391=>391, 392=>392, 393=>393, 394=>394, 395=>395, 396=>396, 397=>397, 398=>398, 399=>399, 400=>400, 401=>401, 402=>402, 403=>403, 404=>404, 405=>405, 406=>406, 407=>407, 408=>408, 409=>409, 410=>410, 411=>411, 412=>412, 413=>413, 414=>414, 415=>415, 416=>416, 417=>417, 418=>418, 419=>419, 420=>420, 421=>421, 422=>422, 423=>423, 424=>424, 425=>425, 426=>426, 427=>427, 428=>428, 429=>429, 430=>430, 431=>431, 432=>432, 433=>433, 434=>434, 435=>435, 436=>436, 437=>437, 438=>438, 439=>439, 440=>440, 441=>441, 442=>442, 443=>443, 444=>444, 445=>445, 446=>446, 447=>447, 448=>448, 449=>449, 450=>450, 451=>451, 452=>452, 453=>453, 454=>454, 455=>455, 456=>456, 457=>457, 458=>458, 459=>459, 460=>460, 461=>461, 462=>462, 463=>463, 464=>464, 465=>465, 466=>466, 467=>467, 468=>468, 469=>469, 470=>470, 471=>471, 472=>472, 473=>473, 474=>474, 475=>475, 476=>476, 477=>477, 478=>478, 479=>479, 480=>480, 481=>481, 482=>482, 483=>483, 484=>484, 485=>485, 486=>486, 487=>487, 488=>488, 489=>489, 490=>490, 491=>491, 492=>492, 493=>493, 494=>494, 495=>495, 496=>496, 497=>497, 498=>498, 499=>499, 500=>500,} +loop_count: 60000 diff --git a/benchmark/vm_block.yml b/benchmark/vm_block.yml new file mode 100644 index 0000000000..68b3e40bf5 --- /dev/null +++ b/benchmark/vm_block.yml @@ -0,0 +1,9 @@ +prelude: | + def m + yield + end +benchmark: + vm_block: | + m{ + } +loop_count: 30000000 diff --git a/benchmark/vm_block_handler.yml b/benchmark/vm_block_handler.yml new file mode 100644 index 0000000000..461d7953ad --- /dev/null +++ b/benchmark/vm_block_handler.yml @@ -0,0 +1,27 @@ +# :FIXME: is there a way to benchmark block_handler_type_ifunc? + +prelude: | + p = proc{_1} + o = Object.new + def o.each + i = 0 + while i < 3_000_000 do + yield i + i += 1 + end + end + +benchmark: + - name: block_handler_type_iseq + script: | + o.each{_1} + + - name: block_handler_type_symbol + script: | + o.each(&:itself) + + - name: block_handler_type_proc + script: | + o.each(&p) + +loop_count: 1 diff --git a/benchmark/vm_blockparam.yml b/benchmark/vm_blockparam.yml new file mode 100644 index 0000000000..5e5a0170a2 --- /dev/null +++ b/benchmark/vm_blockparam.yml @@ -0,0 +1,7 @@ +prelude: | + def m &b + end +benchmark: + vm_blockparam: | + m{} +loop_count: 30000000 diff --git a/benchmark/vm_blockparam_call.yml b/benchmark/vm_blockparam_call.yml new file mode 100644 index 0000000000..a7d8d366ea --- /dev/null +++ b/benchmark/vm_blockparam_call.yml @@ -0,0 +1,8 @@ +prelude: | + def m &b + b.call + end +benchmark: + vm_blockparam_call: | + m{} +loop_count: 30000000 diff --git a/benchmark/vm_blockparam_pass.yml b/benchmark/vm_blockparam_pass.yml new file mode 100644 index 0000000000..841f5e7a63 --- /dev/null +++ b/benchmark/vm_blockparam_pass.yml @@ -0,0 +1,12 @@ +prelude: | + def bp_yield + yield + end + + def bp_pass &b + bp_yield &b + end +benchmark: + vm_blockparam_pass: | + bp_pass{} +loop_count: 30000000 diff --git a/benchmark/vm_blockparam_yield.yml b/benchmark/vm_blockparam_yield.yml new file mode 100644 index 0000000000..8ea9b46ed2 --- /dev/null +++ b/benchmark/vm_blockparam_yield.yml @@ -0,0 +1,8 @@ +prelude: | + def bp_yield &b + yield + end +benchmark: + vm_blockparam_yield: | + bp_yield{} +loop_count: 30000000 diff --git a/benchmark/vm_case.yml b/benchmark/vm_case.yml new file mode 100644 index 0000000000..b26a491a15 --- /dev/null +++ b/benchmark/vm_case.yml @@ -0,0 +1,13 @@ +benchmark: + vm_case: | + case :foo + when :bar + raise + when :baz + raise + when :boo + raise + when :foo + # noop + end +loop_count: 6000000 diff --git a/benchmark/vm_case_classes.yml b/benchmark/vm_case_classes.yml new file mode 100644 index 0000000000..cacc4f0464 --- /dev/null +++ b/benchmark/vm_case_classes.yml @@ -0,0 +1,9 @@ +benchmark: + vm_case_classes: | + case :foo + when Hash + raise + when Array + raise + end +loop_count: 6000000 diff --git a/benchmark/vm_case_lit.yml b/benchmark/vm_case_lit.yml new file mode 100644 index 0000000000..9f91801544 --- /dev/null +++ b/benchmark/vm_case_lit.yml @@ -0,0 +1,23 @@ +# loop_count is not utilized since `i` is involved in the script +benchmark: + vm_case_lit: | + i = 0 + @ret = [ "foo", true, false, :sym, 6, nil, 0.1, 0xffffffffffffffff ] + def foo(i) + @ret[i % @ret.size] + end + + while i<6_000_000 + case foo(i) + when "foo" then :foo + when true then true + when false then false + when :sym then :sym + when 6 then :fix + when nil then nil + when 0.1 then :float + when 0xffffffffffffffff then :big + end + i += 1 + end +loop_count: 1 diff --git a/benchmark/vm3_clearmethodcache.rb b/benchmark/vm_clearmethodcache.rb index 9661323cd2..9661323cd2 100644 --- a/benchmark/vm3_clearmethodcache.rb +++ b/benchmark/vm_clearmethodcache.rb diff --git a/benchmark/vm_const.yml b/benchmark/vm_const.yml new file mode 100644 index 0000000000..8939ca0cd3 --- /dev/null +++ b/benchmark/vm_const.yml @@ -0,0 +1,13 @@ +prelude: | + Const = 1 + A = B = C = D = E = F = G = H = I = J = K = L = M = N = O = P = Q = R = S = T = U = V = W = X = Y = Z = 1 + def foo + A; B; C; D; E; F; G; H; I; J; K; L; M; N; O; P; Q; R; S; T; U; V; W; X; Y; Z + end +benchmark: + vm_const: | + j = Const + k = Const + vm_const_many: | + foo +loop_count: 30000000 diff --git a/benchmark/vm_cvar.yml b/benchmark/vm_cvar.yml new file mode 100644 index 0000000000..1d0e161829 --- /dev/null +++ b/benchmark/vm_cvar.yml @@ -0,0 +1,20 @@ +prelude: | + class A + @@foo = 1 + + def self.foo + @@foo + end + + ("A".."Z").each do |module_name| + eval <<-EOM + module #{module_name} + end + + include #{module_name} + EOM + end + end +benchmark: + vm_cvar: A.foo +loop_count: 600000 diff --git a/benchmark/vm_defined_method.yml b/benchmark/vm_defined_method.yml new file mode 100644 index 0000000000..347e0cfd33 --- /dev/null +++ b/benchmark/vm_defined_method.yml @@ -0,0 +1,8 @@ +prelude: | + class Object + define_method(:m){} + end +benchmark: + vm_defined_method: | + m; m; m; m; m; m; m; m; +loop_count: 6000000 diff --git a/benchmark/vm_dstr.yml b/benchmark/vm_dstr.yml new file mode 100644 index 0000000000..30c7a3193c --- /dev/null +++ b/benchmark/vm_dstr.yml @@ -0,0 +1,6 @@ +prelude: | + x = y = 'z' +benchmark: + vm_dstr: | + str = "foo#{x}bar#{y}baz" +loop_count: 6000000 diff --git a/benchmark/vm_dstr_ary.rb b/benchmark/vm_dstr_ary.rb new file mode 100644 index 0000000000..1d3aa3b97b --- /dev/null +++ b/benchmark/vm_dstr_ary.rb @@ -0,0 +1,6 @@ +i = 0 +x = y = [] +while i<6_000_000 # benchmark loop 2 + i += 1 + str = "foo#{x}bar#{y}baz" +end diff --git a/benchmark/vm_dstr_bool.rb b/benchmark/vm_dstr_bool.rb new file mode 100644 index 0000000000..631ca54755 --- /dev/null +++ b/benchmark/vm_dstr_bool.rb @@ -0,0 +1,7 @@ +i = 0 +x = true +y = false +while i<6_000_000 # benchmark loop 2 + i += 1 + str = "foo#{x}bar#{y}baz" +end diff --git a/benchmark/vm_dstr_class_module.rb b/benchmark/vm_dstr_class_module.rb new file mode 100644 index 0000000000..becf0861c7 --- /dev/null +++ b/benchmark/vm_dstr_class_module.rb @@ -0,0 +1,10 @@ +i = 0 +class A; end unless defined?(A) +module B; end unless defined?(B) +x = A +y = B +while i<6_000_000 # benchmark loop 2 + i += 1 + str = "foo#{x}bar#{y}baz" +end + diff --git a/benchmark/vm_dstr_digit.rb b/benchmark/vm_dstr_digit.rb new file mode 100644 index 0000000000..caaa395192 --- /dev/null +++ b/benchmark/vm_dstr_digit.rb @@ -0,0 +1,7 @@ +i = 0 +x = 0 +y = 9 +while i<6_000_000 # benchmark loop 2 + i += 1 + str = "foo#{x}bar#{y}baz" +end diff --git a/benchmark/vm_dstr_int.rb b/benchmark/vm_dstr_int.rb new file mode 100644 index 0000000000..ed380d7595 --- /dev/null +++ b/benchmark/vm_dstr_int.rb @@ -0,0 +1,5 @@ +i = 0 +while i<6_000_000 # benchmark loop 2 + i += 1 + str = "foo#{i}bar#{i}baz" +end diff --git a/benchmark/vm_dstr_nil.rb b/benchmark/vm_dstr_nil.rb new file mode 100644 index 0000000000..ec4f5d6c67 --- /dev/null +++ b/benchmark/vm_dstr_nil.rb @@ -0,0 +1,6 @@ +i = 0 +x = y = nil +while i<6_000_000 # benchmark loop 2 + i += 1 + str = "foo#{x}bar#{y}baz" +end diff --git a/benchmark/vm_dstr_obj.rb b/benchmark/vm_dstr_obj.rb new file mode 100644 index 0000000000..fb78637ead --- /dev/null +++ b/benchmark/vm_dstr_obj.rb @@ -0,0 +1,6 @@ +i = 0 +x = y = Object.new +while i<6_000_000 # benchmark loop 2 + i += 1 + str = "foo#{x}bar#{y}baz" +end diff --git a/benchmark/vm_dstr_obj_def.rb b/benchmark/vm_dstr_obj_def.rb new file mode 100644 index 0000000000..99ff7b98fb --- /dev/null +++ b/benchmark/vm_dstr_obj_def.rb @@ -0,0 +1,8 @@ +i = 0 +o = Object.new +def o.to_s; -""; end +x = y = o +while i<6_000_000 # benchmark loop 2 + i += 1 + str = "foo#{x}bar#{y}baz" +end diff --git a/benchmark/vm_dstr_str.rb b/benchmark/vm_dstr_str.rb new file mode 100644 index 0000000000..45fc107892 --- /dev/null +++ b/benchmark/vm_dstr_str.rb @@ -0,0 +1,6 @@ +i = 0 +x = y = "" +while i<6_000_000 # benchmark loop 2 + i += 1 + str = "foo#{x}bar#{y}baz" +end diff --git a/benchmark/vm_dstr_sym.rb b/benchmark/vm_dstr_sym.rb new file mode 100644 index 0000000000..484b8f8150 --- /dev/null +++ b/benchmark/vm_dstr_sym.rb @@ -0,0 +1,6 @@ +i = 0 +x = y = :z +while i<6_000_000 # benchmark loop 2 + i += 1 + str = "foo#{x}bar#{y}baz" +end diff --git a/benchmark/vm_ensure.yml b/benchmark/vm_ensure.yml new file mode 100644 index 0000000000..4ea62f30de --- /dev/null +++ b/benchmark/vm_ensure.yml @@ -0,0 +1,14 @@ +# Not utilizing loop_count since using it for this is too unstable for now +benchmark: + vm_ensure: | + i = 0 + while i<30_000_000 + i += 1 + begin + begin + ensure + end + ensure + end + end +loop_count: 1 diff --git a/benchmark/vm_eval.yml b/benchmark/vm_eval.yml new file mode 100644 index 0000000000..7ba1a8d1de --- /dev/null +++ b/benchmark/vm_eval.yml @@ -0,0 +1,4 @@ +benchmark: + vm_eval: | + eval("1") +loop_count: 6000000 diff --git a/benchmark/vm_fiber_allocate.yml b/benchmark/vm_fiber_allocate.yml new file mode 100644 index 0000000000..b5a54e1ddf --- /dev/null +++ b/benchmark/vm_fiber_allocate.yml @@ -0,0 +1,8 @@ +prelude: | + # Disable GC to see raw throughput: + GC.disable +benchmark: + vm_fiber_allocate: | + fiber = Fiber.new{Fiber.yield} + fiber.resume +loop_count: 100000 diff --git a/benchmark/vm_fiber_count.yml b/benchmark/vm_fiber_count.yml new file mode 100644 index 0000000000..b83d3152d4 --- /dev/null +++ b/benchmark/vm_fiber_count.yml @@ -0,0 +1,10 @@ +# On Linux, you will need to increase the maximum number of memory maps: +# sudo sysctl -w vm.max_map_count=200000 +prelude: | + fibers = [] +benchmark: + vm_fiber_count: | + fiber = Fiber.new{Fiber.yield} + fibers << fiber + fiber.resume +loop_count: 100000 diff --git a/benchmark/vm_fiber_reuse.yml b/benchmark/vm_fiber_reuse.yml new file mode 100644 index 0000000000..4ca41085b1 --- /dev/null +++ b/benchmark/vm_fiber_reuse.yml @@ -0,0 +1,14 @@ +prelude: | + GC.disable + fibers = [] +benchmark: + vm_fiber_reuse: | + 1024.times do + fiber = Fiber.new{Fiber.yield} + fibers << fiber + fiber.resume + end + + fibers.clear + GC.start +loop_count: 200 diff --git a/benchmark/vm_fiber_reuse_gc.yml b/benchmark/vm_fiber_reuse_gc.yml new file mode 100644 index 0000000000..892622f121 --- /dev/null +++ b/benchmark/vm_fiber_reuse_gc.yml @@ -0,0 +1,12 @@ +# https://bugs.ruby-lang.org/issues/16009 +prelude: | + fibers = [] +benchmark: + vm_fiber_reuse_gc: | + 2000.times do + fiber = Fiber.new{Fiber.yield} + fibers << fiber + fiber.resume + end + fibers.clear +loop_count: 100 diff --git a/benchmark/vm_fiber_switch.yml b/benchmark/vm_fiber_switch.yml new file mode 100644 index 0000000000..3de36b66eb --- /dev/null +++ b/benchmark/vm_fiber_switch.yml @@ -0,0 +1,9 @@ +prelude: | + # based on benchmark for [ruby-core:65518] [Feature #10341] by Knut Franke + fib = Fiber.new do + loop { Fiber.yield } + end +benchmark: + vm_fiber_switch: | + fib.resume +loop_count: 20000000 diff --git a/benchmark/vm_float_simple.yml b/benchmark/vm_float_simple.yml new file mode 100644 index 0000000000..92f5fd52ab --- /dev/null +++ b/benchmark/vm_float_simple.yml @@ -0,0 +1,8 @@ +prelude: | + f = 0.0 +benchmark: + vm_float_simple: | + f += 0.1; f -= 0.1 + f += 0.1; f -= 0.1 + f += 0.1; f -= 0.1 +loop_count: 30000000 diff --git a/benchmark/vm_freezeobj.yml b/benchmark/vm_freezeobj.yml new file mode 100644 index 0000000000..69a795a354 --- /dev/null +++ b/benchmark/vm_freezeobj.yml @@ -0,0 +1,6 @@ +prelude: | + objs = 100000.times.map { Object.new } +benchmark: + vm_freeze_obj: | + objs.map(&:freeze) +loop_count: 600 diff --git a/benchmark/vm_freezestring.yml b/benchmark/vm_freezestring.yml new file mode 100644 index 0000000000..facc9aa043 --- /dev/null +++ b/benchmark/vm_freezestring.yml @@ -0,0 +1,10 @@ +prelude: | + class String + def freeze + -self + end + end +benchmark: + vm_freezestring: | + "tXnL1BP5T1WPXMjuFNLQtallEtRcay1t2lHtJSrlVsDgvunlbtfpr/DGdH0NGYE9".freeze +loop_count: 6000000 diff --git a/benchmark/vm3_gc.rb b/benchmark/vm_gc.rb index e668026915..e668026915 100644 --- a/benchmark/vm3_gc.rb +++ b/benchmark/vm_gc.rb diff --git a/benchmark/vm3_gc_old_full.rb b/benchmark/vm_gc_old_full.rb index cfdfc8c5a5..cfdfc8c5a5 100644 --- a/benchmark/vm3_gc_old_full.rb +++ b/benchmark/vm_gc_old_full.rb diff --git a/benchmark/vm3_gc_old_immediate.rb b/benchmark/vm_gc_old_immediate.rb index ad22feb655..ad22feb655 100644 --- a/benchmark/vm3_gc_old_immediate.rb +++ b/benchmark/vm_gc_old_immediate.rb diff --git a/benchmark/vm3_gc_old_lazy.rb b/benchmark/vm_gc_old_lazy.rb index b74d44baf1..b74d44baf1 100644 --- a/benchmark/vm3_gc_old_lazy.rb +++ b/benchmark/vm_gc_old_lazy.rb diff --git a/benchmark/vm_gc_short_lived.yml b/benchmark/vm_gc_short_lived.yml new file mode 100644 index 0000000000..29c803fee3 --- /dev/null +++ b/benchmark/vm_gc_short_lived.yml @@ -0,0 +1,9 @@ +benchmark: + vm_gc_short_lived: | + a = '' # short-lived String + b = '' + c = '' + d = '' + e = '' + f = '' +loop_count: 30000000 diff --git a/benchmark/vm_gc_short_with_complex_long.yml b/benchmark/vm_gc_short_with_complex_long.yml new file mode 100644 index 0000000000..4b6c3ed7b9 --- /dev/null +++ b/benchmark/vm_gc_short_with_complex_long.yml @@ -0,0 +1,25 @@ +prelude: | + def nested_hash h, n + if n == 0 + '' + else + 10.times{ + h[Object.new] = nested_hash(h, n-1) + } + end + end + + long_lived = Hash.new + nested_hash long_lived, 6 + + GC.start + GC.start +benchmark: + vm_gc_short_with_complex_long: | + a = '' # short-lived String + b = '' + c = '' + d = '' + e = '' + f = '' +loop_count: 30000000 diff --git a/benchmark/vm_gc_short_with_long.yml b/benchmark/vm_gc_short_with_long.yml new file mode 100644 index 0000000000..03ba0f95a9 --- /dev/null +++ b/benchmark/vm_gc_short_with_long.yml @@ -0,0 +1,13 @@ +prelude: | + long_lived = Array.new(1_000_000){|i| "#{i}"} + GC.start + GC.start +benchmark: + vm_gc_short_with_long: | + a = '' # short-lived String + b = '' + c = '' + d = '' + e = '' + f = '' +loop_count: 30000000 diff --git a/benchmark/vm_gc_short_with_symbol.yml b/benchmark/vm_gc_short_with_symbol.yml new file mode 100644 index 0000000000..129b8bf4ed --- /dev/null +++ b/benchmark/vm_gc_short_with_symbol.yml @@ -0,0 +1,13 @@ +prelude: | + 50_000.times{|i| sym = "sym#{i}".to_sym} + GC.start + GC.start +benchmark: + vm_gc_short_with_symbol: | + a = '' # short-lived String + b = '' + c = '' + d = '' + e = '' + f = '' +loop_count: 30000000 diff --git a/benchmark/vm_gc_wb_ary.yml b/benchmark/vm_gc_wb_ary.yml new file mode 100644 index 0000000000..e3293e72d0 --- /dev/null +++ b/benchmark/vm_gc_wb_ary.yml @@ -0,0 +1,12 @@ +prelude: | + short_lived_ary = [] + + if RUBY_VERSION >= "2.2.0" + GC.start(full_mark: false, immediate_mark: true, immediate_sweep: true) + end + + short_lived = '' +benchmark: + vm_gc_wb_ary: | + short_lived_ary[0] = short_lived # write barrier +loop_count: 30000000 diff --git a/benchmark/vm_gc_wb_ary_promoted.yml b/benchmark/vm_gc_wb_ary_promoted.yml new file mode 100644 index 0000000000..003995945b --- /dev/null +++ b/benchmark/vm_gc_wb_ary_promoted.yml @@ -0,0 +1,15 @@ +prelude: | + long_lived = [] + + if RUBY_VERSION > "2.2.0" + 3.times{ GC.start(full_mark: false, immediate_mark: true, immediate_sweep: true) } + elsif + GC.start + end + + short_lived = '' + +benchmark: + vm_gc_wb_ary_promoted: | + long_lived[0] = short_lived # write barrier +loop_count: 30000000 diff --git a/benchmark/vm_gc_wb_obj.yml b/benchmark/vm_gc_wb_obj.yml new file mode 100644 index 0000000000..a2a2ce2d18 --- /dev/null +++ b/benchmark/vm_gc_wb_obj.yml @@ -0,0 +1,15 @@ +prelude: | + class C + attr_accessor :foo + end + short_lived_obj = C.new + + if RUBY_VERSION >= "2.2.0" + GC.start(full_mark: false, immediate_mark: true, immediate_sweep: true) + end + + short_lived = '' +benchmark: + vm_gc_wb_obj: | + short_lived_obj.foo = short_lived # write barrier +loop_count: 30000000 diff --git a/benchmark/vm_gc_wb_obj_promoted.yml b/benchmark/vm_gc_wb_obj_promoted.yml new file mode 100644 index 0000000000..00a454ba72 --- /dev/null +++ b/benchmark/vm_gc_wb_obj_promoted.yml @@ -0,0 +1,17 @@ +prelude: | + class C + attr_accessor :foo + end + long_lived = C.new + + if RUBY_VERSION >= "2.2.0" + 3.times{ GC.start(full_mark: false, immediate_mark: true, immediate_sweep: true) } + elsif + GC.start + end + + short_lived = '' +benchmark: + vm_gc_wb_obj_promoted: | + long_lived.foo = short_lived # write barrier +loop_count: 30000000 diff --git a/benchmark/vm_iclass_super.yml b/benchmark/vm_iclass_super.yml new file mode 100644 index 0000000000..21bb7db247 --- /dev/null +++ b/benchmark/vm_iclass_super.yml @@ -0,0 +1,20 @@ +prelude: | + class C + def m + 1 + end + + ("A".."M").each do |module_name| + eval <<-EOM + module #{module_name} + def m; super; end + end + prepend #{module_name} + EOM + end + end + + obj = C.new +benchmark: + vm_iclass_super: obj.m +loop_count: 6000000 diff --git a/benchmark/vm_ivar.yml b/benchmark/vm_ivar.yml new file mode 100644 index 0000000000..119531d5ef --- /dev/null +++ b/benchmark/vm_ivar.yml @@ -0,0 +1,6 @@ +prelude: "@a = 1\n" +benchmark: + vm_ivar: | + j = @a + k = @a +loop_count: 30000000 diff --git a/benchmark/vm_ivar_embedded_obj_init.yml b/benchmark/vm_ivar_embedded_obj_init.yml new file mode 100644 index 0000000000..74fe20a630 --- /dev/null +++ b/benchmark/vm_ivar_embedded_obj_init.yml @@ -0,0 +1,14 @@ +prelude: | + class C + def set_ivars + @a = nil + @b = nil + @c = nil + end + end + + c = C.new +benchmark: + vm_ivar_embedded_obj_init: | + c.set_ivars +loop_count: 30000000 diff --git a/benchmark/vm_ivar_extended_obj_init.yml b/benchmark/vm_ivar_extended_obj_init.yml new file mode 100644 index 0000000000..f054bab282 --- /dev/null +++ b/benchmark/vm_ivar_extended_obj_init.yml @@ -0,0 +1,16 @@ +prelude: | + class C + def set_ivars + @a = nil + @b = nil + @c = nil + @d = nil + @e = nil + end + end + + c = C.new +benchmark: + vm_ivar_extended_obj_init: | + c.set_ivars +loop_count: 30000000 diff --git a/benchmark/vm_ivar_generic_get.yml b/benchmark/vm_ivar_generic_get.yml new file mode 100644 index 0000000000..dae2d37671 --- /dev/null +++ b/benchmark/vm_ivar_generic_get.yml @@ -0,0 +1,17 @@ +prelude: | + class C < Array + attr_reader :a, :b, :c + def initialize + @a = nil + @b = nil + @c = nil + end + end + + c = C.new +benchmark: + vm_ivar_generic_get: | + c.a + c.b + c.c +loop_count: 30000000 diff --git a/benchmark/vm_ivar_generic_set.yml b/benchmark/vm_ivar_generic_set.yml new file mode 100644 index 0000000000..102a6577fb --- /dev/null +++ b/benchmark/vm_ivar_generic_set.yml @@ -0,0 +1,14 @@ +prelude: | + class C < Array + def set_ivars + @a = nil + @b = nil + @c = nil + end + end + + c = C.new +benchmark: + vm_ivar_generic_set: | + c.set_ivars +loop_count: 30000000 diff --git a/benchmark/vm_ivar_get.yml b/benchmark/vm_ivar_get.yml new file mode 100644 index 0000000000..9174af6965 --- /dev/null +++ b/benchmark/vm_ivar_get.yml @@ -0,0 +1,37 @@ +prelude: | + class Example + def initialize + @v0 = 1 + @v1 = 2 + @v3 = 3 + @levar = 1 + end + + def get_value_loop + sum = 0 + + i = 0 + while i < 1000000 + # 10 times to de-emphasize loop overhead + sum += @levar + sum += @levar + sum += @levar + sum += @levar + sum += @levar + sum += @levar + sum += @levar + sum += @levar + sum += @levar + sum += @levar + i += 1 + end + + return sum + end + end + + obj = Example.new +benchmark: + vm_ivar_get: | + obj.get_value_loop +loop_count: 100 diff --git a/benchmark/vm_ivar_get_unintialized.yml b/benchmark/vm_ivar_get_unintialized.yml new file mode 100644 index 0000000000..a1ccfb06ce --- /dev/null +++ b/benchmark/vm_ivar_get_unintialized.yml @@ -0,0 +1,12 @@ +prelude: | + class Example + def read + @uninitialized + end + end + + obj = Example.new +benchmark: + vm_ivar_get_uninitialized: | + obj.read +loop_count: 30000000 diff --git a/benchmark/vm_ivar_lazy_set.yml b/benchmark/vm_ivar_lazy_set.yml new file mode 100644 index 0000000000..7372ffcfbc --- /dev/null +++ b/benchmark/vm_ivar_lazy_set.yml @@ -0,0 +1,12 @@ +prelude: | + class Example + def lazy_set + @uninitialized ||= 123 + end + end + + objs = 10000000.times.map { Example.new } +benchmark: + vm_ivar_lazy_set: | + objs.each(&:lazy_set) +loop_count: 1 diff --git a/benchmark/vm_ivar_of_class.yml b/benchmark/vm_ivar_of_class.yml new file mode 100644 index 0000000000..172e28b2fd --- /dev/null +++ b/benchmark/vm_ivar_of_class.yml @@ -0,0 +1,12 @@ +prelude: | + class C + @a = 1 + def self.a + _a = @a; _a = @a; _a = @a; _a = @a; _a = @a; + _a = @a; _a = @a; _a = @a; _a = @a; _a = @a; + end + end +benchmark: + vm_ivar_of_class: | + a = C.a +loop_count: 30000000 diff --git a/benchmark/vm_ivar_of_class_set.yml b/benchmark/vm_ivar_of_class_set.yml new file mode 100644 index 0000000000..2ea5199423 --- /dev/null +++ b/benchmark/vm_ivar_of_class_set.yml @@ -0,0 +1,11 @@ +prelude: | + class C + @a = 1 + def self.a o + @a = o; @a = o; @a = o; @a = o; @a = o; @a = o; + end + end +benchmark: + vm_ivar_of_class_set: | + a = C.a(nil) +loop_count: 30000000 diff --git a/benchmark/vm_ivar_set.yml b/benchmark/vm_ivar_set.yml new file mode 100644 index 0000000000..8bbb60043b --- /dev/null +++ b/benchmark/vm_ivar_set.yml @@ -0,0 +1,5 @@ +benchmark: + vm_ivar_set: | + @a = 1 + @b = 2 +loop_count: 30000000 diff --git a/benchmark/vm_ivar_set_on_instance.yml b/benchmark/vm_ivar_set_on_instance.yml new file mode 100644 index 0000000000..91857b7742 --- /dev/null +++ b/benchmark/vm_ivar_set_on_instance.yml @@ -0,0 +1,35 @@ +prelude: | + class TheClass + def initialize + @v0 = 1 + @v1 = 2 + @v3 = 3 + @levar = 1 + end + + def set_value_loop + # 1M + i = 0 + while i < 1000000 + # 10 times to de-emphasize loop overhead + @levar = i + @levar = i + @levar = i + @levar = i + @levar = i + @levar = i + @levar = i + @levar = i + @levar = i + @levar = i + i += 1 + end + end + end + + obj = TheClass.new + +benchmark: + vm_ivar_set_on_instance: | + obj.set_value_loop +loop_count: 100 diff --git a/benchmark/vm_ivar_set_subclass.yml b/benchmark/vm_ivar_set_subclass.yml new file mode 100644 index 0000000000..bc8bf5bf6b --- /dev/null +++ b/benchmark/vm_ivar_set_subclass.yml @@ -0,0 +1,20 @@ +prelude: | + class A + def set_ivars + @a = nil + @b = nil + @c = nil + @d = nil + @e = nil + end + end + class B < A; end + class C < A; end + + b = B.new + c = C.new +benchmark: + vm_ivar_init_subclass: | + b.set_ivars + c.set_ivars +loop_count: 3000000 diff --git a/benchmark/vm_length.yml b/benchmark/vm_length.yml new file mode 100644 index 0000000000..5fd94e7d86 --- /dev/null +++ b/benchmark/vm_length.yml @@ -0,0 +1,8 @@ +prelude: | + a = 'abc' + b = [1, 2, 3] +benchmark: + vm_length: | + a.length + b.length +loop_count: 30000000 diff --git a/benchmark/vm_lvar_cond_set.yml b/benchmark/vm_lvar_cond_set.yml new file mode 100644 index 0000000000..1845f9d12e --- /dev/null +++ b/benchmark/vm_lvar_cond_set.yml @@ -0,0 +1,8 @@ +benchmark: + vm_lvar_cond_set: | + a ||= 1 + b ||= 1 + c ||= 1 + d ||= 1 + nil +loop_count: 30000000 diff --git a/benchmark/vm_lvar_init.yml b/benchmark/vm_lvar_init.yml new file mode 100644 index 0000000000..70a9b1c0ca --- /dev/null +++ b/benchmark/vm_lvar_init.yml @@ -0,0 +1,21 @@ +# while loop cost is not removed because `i` is used in the script +benchmark: + vm_lvar_init: | + def m v + unless v + # unreachable code + v1 = v2 = v3 = v4 = v5 = v6 = v7 = v8 = v9 = v10 = + v11 = v12 = v13 = v14 = v15 = v16 = v17 = v18 = v19 = v20 = + v21 = v22 = v23 = v24 = v25 = v26 = v27 = v28 = v29 = v30 = + v31 = v32 = v33 = v34 = v35 = v36 = v37 = v38 = v39 = v40 = + v41 = v42 = v43 = v44 = v45 = v46 = v47 = v48 = v49 = v50 = 1 + end + end + + i = 0 + + while i<30_000_000 + i += 1 + m i + end +loop_count: 1 diff --git a/benchmark/vm_lvar_set.yml b/benchmark/vm_lvar_set.yml new file mode 100644 index 0000000000..f29f763d81 --- /dev/null +++ b/benchmark/vm_lvar_set.yml @@ -0,0 +1,4 @@ +benchmark: + vm_lvar_set: | + a = b = c = d = e = f = g = h = j = k = l = m = n = o = p = q = r = 1 +loop_count: 30000000 diff --git a/benchmark/vm_method.yml b/benchmark/vm_method.yml new file mode 100644 index 0000000000..d45e4ec572 --- /dev/null +++ b/benchmark/vm_method.yml @@ -0,0 +1,8 @@ +prelude: | + def m + nil + end +benchmark: + vm_method: | + m; m; m; m; m; m; m; m; +loop_count: 6000000 diff --git a/benchmark/vm_method_missing.yml b/benchmark/vm_method_missing.yml new file mode 100644 index 0000000000..3da456c0bb --- /dev/null +++ b/benchmark/vm_method_missing.yml @@ -0,0 +1,11 @@ +prelude: | + class C + def method_missing mid + end + end + + obj = C.new +benchmark: + vm_method_missing: | + obj.m; obj.m; obj.m; obj.m; obj.m; obj.m; obj.m; obj.m; +loop_count: 6000000 diff --git a/benchmark/vm_method_with_block.yml b/benchmark/vm_method_with_block.yml new file mode 100644 index 0000000000..281a481394 --- /dev/null +++ b/benchmark/vm_method_with_block.yml @@ -0,0 +1,8 @@ +prelude: | + def m + nil + end +benchmark: + vm_method_with_block: | + m{}; m{}; m{}; m{}; m{}; m{}; m{}; m{}; +loop_count: 6000000 diff --git a/benchmark/vm_module_ann_const_set.yml b/benchmark/vm_module_ann_const_set.yml new file mode 100644 index 0000000000..243229ba4a --- /dev/null +++ b/benchmark/vm_module_ann_const_set.yml @@ -0,0 +1,4 @@ +benchmark: + vm_module_ann_const_set: | + Module.new.const_set(:X, Module.new) +loop_count: 6000000 diff --git a/benchmark/vm_module_const_set.yml b/benchmark/vm_module_const_set.yml new file mode 100644 index 0000000000..e5a24181a9 --- /dev/null +++ b/benchmark/vm_module_const_set.yml @@ -0,0 +1,8 @@ +prelude: | + module M + end + $VERBOSE = nil +benchmark: + vm_module_const_set: | + M.const_set(:X, Module.new) +loop_count: 6000000 diff --git a/benchmark/vm_mutex.yml b/benchmark/vm_mutex.yml new file mode 100644 index 0000000000..abcf1e28ce --- /dev/null +++ b/benchmark/vm_mutex.yml @@ -0,0 +1,8 @@ +prelude: | + require 'thread' + + m = Thread::Mutex.new +benchmark: + vm_mutex: | + m.synchronize{} +loop_count: 6000000 diff --git a/benchmark/vm_neq.yml b/benchmark/vm_neq.yml new file mode 100644 index 0000000000..fb04d15ae8 --- /dev/null +++ b/benchmark/vm_neq.yml @@ -0,0 +1,7 @@ +prelude: | + obj1 = Object.new + obj2 = Object.new +benchmark: + vm_neq: | + obj1 != obj2 +loop_count: 30000000 diff --git a/benchmark/vm_newlambda.yml b/benchmark/vm_newlambda.yml new file mode 100644 index 0000000000..0b9787d91a --- /dev/null +++ b/benchmark/vm_newlambda.yml @@ -0,0 +1,4 @@ +benchmark: + vm_newlambda: | + lambda {} +loop_count: 6000000 diff --git a/benchmark/vm_not.yml b/benchmark/vm_not.yml new file mode 100644 index 0000000000..c68dde3c50 --- /dev/null +++ b/benchmark/vm_not.yml @@ -0,0 +1,6 @@ +prelude: | + obj = Object.new +benchmark: + vm_not: | + !obj +loop_count: 30000000 diff --git a/benchmark/vm_poly_method.yml b/benchmark/vm_poly_method.yml new file mode 100644 index 0000000000..dd2f4e71de --- /dev/null +++ b/benchmark/vm_poly_method.yml @@ -0,0 +1,24 @@ +# loop_count is not utilized since `i` is involved in the script +benchmark: + vm_poly_method: | + class C1 + def m + 1 + end + end + class C2 + def m + 2 + end + end + + o1 = C1.new + o2 = C2.new + + i = 0 + while i<6_000_000 + o = (i % 2 == 0) ? o1 : o2 + o.m; o.m; o.m; o.m; o.m; o.m; o.m; o.m + i += 1 + end +loop_count: 1 diff --git a/benchmark/vm_poly_method_ov.yml b/benchmark/vm_poly_method_ov.yml new file mode 100644 index 0000000000..bca1b62729 --- /dev/null +++ b/benchmark/vm_poly_method_ov.yml @@ -0,0 +1,24 @@ +# loop_count is not utilized since `i` is involved in the script +benchmark: + vm_poly_method_ov: | + class C1 + def m + 1 + end + end + class C2 + def m + 2 + end + end + + o1 = C1.new + o2 = C2.new + + i = 0 + while i<6_000_000 + o = (i % 2 == 0) ? o1 : o2 + # o.m; o.m; o.m; o.m; o.m; o.m; o.m; o.m + i += 1 + end +loop_count: 1 diff --git a/benchmark/vm_poly_same_method.yml b/benchmark/vm_poly_same_method.yml new file mode 100644 index 0000000000..6c5404ac84 --- /dev/null +++ b/benchmark/vm_poly_same_method.yml @@ -0,0 +1,25 @@ +prelude: | + module AR; end + class AR::Base + def create_or_update + nil + end + def save + create_or_update + end + end + class Foo < AR::Base; end + class Bar < AR::Base; end + o1 = Foo.new + o2 = Bar.new +benchmark: + vm_poly_same_method: | + o1.save; o2.save; + o1.save; o2.save; + o1.save; o2.save; + o1.save; o2.save; + o1.save; o2.save; + o1.save; o2.save; + o1.save; o2.save; + o1.save; o2.save; +loop_count: 6000000 diff --git a/benchmark/vm_poly_singleton.yml b/benchmark/vm_poly_singleton.yml new file mode 100644 index 0000000000..c7923160fb --- /dev/null +++ b/benchmark/vm_poly_singleton.yml @@ -0,0 +1,18 @@ +# loop_count is not utilized since `i` is involved in the script +benchmark: + vm_poly_singleton: | + class C1 + def m; 1; end + end + + o1 = C1.new + o2 = C1.new + o2.singleton_class + + i = 0 + while i<6_000_000 # benchmark loop 2 + o = (i % 2 == 0) ? o1 : o2 + o.m; o.m; o.m; o.m; o.m; o.m; o.m; o.m + i += 1 + end +loop_count: 1 diff --git a/benchmark/vm_proc.yml b/benchmark/vm_proc.yml new file mode 100644 index 0000000000..2f8de6c272 --- /dev/null +++ b/benchmark/vm_proc.yml @@ -0,0 +1,12 @@ +prelude: | + def m &b + b + end + + pr = m{ + a = 1 + } +benchmark: + vm_proc: | + pr.call +loop_count: 6000000 diff --git a/benchmark/vm_raise1.yml b/benchmark/vm_raise1.yml new file mode 100644 index 0000000000..247d9f70ee --- /dev/null +++ b/benchmark/vm_raise1.yml @@ -0,0 +1,16 @@ +prelude: | + def rec n + if n > 0 + rec n-1 + else + raise + end + end +benchmark: + vm_raise1: | + begin + rec 1 + rescue + # ignore + end +loop_count: 6000000 diff --git a/benchmark/vm_raise2.yml b/benchmark/vm_raise2.yml new file mode 100644 index 0000000000..f0fa047b3c --- /dev/null +++ b/benchmark/vm_raise2.yml @@ -0,0 +1,16 @@ +prelude: | + def rec n + if n > 0 + rec n-1 + else + raise + end + end +benchmark: + vm_raise2: | + begin + rec 10 + rescue + # ignore + end +loop_count: 6000000 diff --git a/benchmark/vm_regexp.yml b/benchmark/vm_regexp.yml new file mode 100644 index 0000000000..2aa3d94dbd --- /dev/null +++ b/benchmark/vm_regexp.yml @@ -0,0 +1,8 @@ +prelude: | + str = 'xxxhogexxx' +benchmark: + vm_regexp: | + /hoge/ =~ str + vm_regexp_invert: | + str =~ /hoge/ +loop_count: 6000000 diff --git a/benchmark/vm_rescue.yml b/benchmark/vm_rescue.yml new file mode 100644 index 0000000000..b4a0af521f --- /dev/null +++ b/benchmark/vm_rescue.yml @@ -0,0 +1,6 @@ +benchmark: + vm_rescue: | + begin + rescue + end +loop_count: 30000000 diff --git a/benchmark/vm_send.yml b/benchmark/vm_send.yml new file mode 100644 index 0000000000..f31bc7ac89 --- /dev/null +++ b/benchmark/vm_send.yml @@ -0,0 +1,14 @@ +prelude: | + class C + def m + end + end + + o = C.new + m = :m +benchmark: + vm_send: | + o.__send__ :m + vm_send_var: | + o.__send__ m +loop_count: 6000000 diff --git a/benchmark/vm_send_cfunc.yml b/benchmark/vm_send_cfunc.yml new file mode 100644 index 0000000000..b114ac317d --- /dev/null +++ b/benchmark/vm_send_cfunc.yml @@ -0,0 +1,3 @@ +benchmark: + vm_send_cfunc: self.class +loop_count: 100000000 diff --git a/benchmark/vm_simplereturn.yml b/benchmark/vm_simplereturn.yml new file mode 100644 index 0000000000..c9829cff0b --- /dev/null +++ b/benchmark/vm_simplereturn.yml @@ -0,0 +1,7 @@ +prelude: | + def m + return 1 + end +benchmark: + vm_simplereturn: m +loop_count: 30000000 diff --git a/benchmark/vm_string_literal.yml b/benchmark/vm_string_literal.yml new file mode 100644 index 0000000000..64439c7980 --- /dev/null +++ b/benchmark/vm_string_literal.yml @@ -0,0 +1,4 @@ +benchmark: + vm_string_literal: | + x = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +loop_count: 6000000 diff --git a/benchmark/vm_struct_big_aref_hi.yml b/benchmark/vm_struct_big_aref_hi.yml new file mode 100644 index 0000000000..4cf78970cb --- /dev/null +++ b/benchmark/vm_struct_big_aref_hi.yml @@ -0,0 +1,7 @@ +prelude: | + s = Struct.new(*('a'..'z').map { |x| x.to_sym }) + x = s.new +benchmark: + vm_struct_big_aref_hi: | + x.z # x[25] +loop_count: 6000000 diff --git a/benchmark/vm_struct_big_aref_lo.yml b/benchmark/vm_struct_big_aref_lo.yml new file mode 100644 index 0000000000..c91af27fa5 --- /dev/null +++ b/benchmark/vm_struct_big_aref_lo.yml @@ -0,0 +1,7 @@ +prelude: | + s = Struct.new(*('a'..'z').map { |x| x.to_sym }) + x = s.new +benchmark: + vm_struct_big_aref_lo: | + x.k # x[10] +loop_count: 6000000 diff --git a/benchmark/vm_struct_big_aset.yml b/benchmark/vm_struct_big_aset.yml new file mode 100644 index 0000000000..69550d14ea --- /dev/null +++ b/benchmark/vm_struct_big_aset.yml @@ -0,0 +1,11 @@ +# loop_count is not utilized since `i` is involved in the script +benchmark: + vm_struct_big_aset: | + s = Struct.new(*('a'..'z').map { |x| x.to_sym }) + x = s.new + i = 0 + while i<6_000_000 + i += 1 + x.k = i # x[10] = i + end +loop_count: 1 diff --git a/benchmark/vm_struct_big_href_hi.yml b/benchmark/vm_struct_big_href_hi.yml new file mode 100644 index 0000000000..09b764dd13 --- /dev/null +++ b/benchmark/vm_struct_big_href_hi.yml @@ -0,0 +1,7 @@ +prelude: | + s = Struct.new(*('a'..'z').map { |x| x.to_sym }) + x = s.new +benchmark: + vm_struct_big_href_hi: | + x[:z] +loop_count: 6000000 diff --git a/benchmark/vm_struct_big_href_lo.yml b/benchmark/vm_struct_big_href_lo.yml new file mode 100644 index 0000000000..d2f00b220f --- /dev/null +++ b/benchmark/vm_struct_big_href_lo.yml @@ -0,0 +1,7 @@ +prelude: | + s = Struct.new(*('a'..'z').map { |x| x.to_sym }) + x = s.new +benchmark: + vm_struct_big_href_lo: | + x[:k] +loop_count: 6000000 diff --git a/benchmark/vm_struct_big_hset.yml b/benchmark/vm_struct_big_hset.yml new file mode 100644 index 0000000000..fc45cbee9c --- /dev/null +++ b/benchmark/vm_struct_big_hset.yml @@ -0,0 +1,11 @@ +# loop_count is not utilized since `i` is involved in the script +benchmark: + vm_struct_big_hset: | + s = Struct.new(*('a'..'z').map { |x| x.to_sym }) + x = s.new + i = 0 + while i<6_000_000 + i += 1 + x[:k] = i + end +loop_count: 1 diff --git a/benchmark/vm_struct_small_aref.yml b/benchmark/vm_struct_small_aref.yml new file mode 100644 index 0000000000..5a83251d1e --- /dev/null +++ b/benchmark/vm_struct_small_aref.yml @@ -0,0 +1,7 @@ +prelude: | + s = Struct.new(:a, :b, :c) + x = s.new +benchmark: + vm_struct_small_aref: | + x.a +loop_count: 6000000 diff --git a/benchmark/vm_struct_small_aset.yml b/benchmark/vm_struct_small_aset.yml new file mode 100644 index 0000000000..74f435f126 --- /dev/null +++ b/benchmark/vm_struct_small_aset.yml @@ -0,0 +1,11 @@ +# loop_count is not utilized since `i` is involved in the script +benchmark: + vm_struct_small_aset: | + s = Struct.new(:a, :b, :c) + x = s.new + i = 0 + while i<6_000_000 + i += 1 + x.a = i + end +loop_count: 1 diff --git a/benchmark/vm_struct_small_href.yml b/benchmark/vm_struct_small_href.yml new file mode 100644 index 0000000000..6b7d7f39e7 --- /dev/null +++ b/benchmark/vm_struct_small_href.yml @@ -0,0 +1,7 @@ +prelude: | + s = Struct.new(:a, :b, :c) + x = s.new +benchmark: + vm_struct_small_href: | + x[:a] +loop_count: 6000000 diff --git a/benchmark/vm_struct_small_hset.yml b/benchmark/vm_struct_small_hset.yml new file mode 100644 index 0000000000..5d43b150de --- /dev/null +++ b/benchmark/vm_struct_small_hset.yml @@ -0,0 +1,7 @@ +prelude: | + s = Struct.new(:a, :b, :c) + x = s.new +benchmark: + vm_struct_small_hset: | + x[:a] = 1 +loop_count: 6000000 diff --git a/benchmark/vm_super.yml b/benchmark/vm_super.yml new file mode 100644 index 0000000000..0d1e965c6e --- /dev/null +++ b/benchmark/vm_super.yml @@ -0,0 +1,17 @@ +prelude: | + class C + def m + 1 + end + end + + class CC < C + def m + super() + end + end + + obj = CC.new +benchmark: + vm_super: obj.m +loop_count: 6000000 diff --git a/benchmark/vm_swap.yml b/benchmark/vm_swap.yml new file mode 100644 index 0000000000..e824a65e0a --- /dev/null +++ b/benchmark/vm_swap.yml @@ -0,0 +1,7 @@ +prelude: | + a = 1 + b = 2 +benchmark: + vm_swap: | + a, b = b, a +loop_count: 30000000 diff --git a/benchmark/vm_thread_alive_check.yml b/benchmark/vm_thread_alive_check.yml new file mode 100644 index 0000000000..d21737d3e8 --- /dev/null +++ b/benchmark/vm_thread_alive_check.yml @@ -0,0 +1,8 @@ +benchmark: + vm_thread_alive_check: | + t = Thread.new{} + while t.alive? + Thread.pass + end +loop_count: 50_000 + diff --git a/benchmark/vm_thread_alive_check1.rb b/benchmark/vm_thread_alive_check1.rb deleted file mode 100644 index c993accdda..0000000000 --- a/benchmark/vm_thread_alive_check1.rb +++ /dev/null @@ -1,6 +0,0 @@ -5_000.times{ - t = Thread.new{} - while t.alive? - Thread.pass - end -} diff --git a/benchmark/vm_thread_condvar1.rb b/benchmark/vm_thread_condvar1.rb index cf5706b23e..feed27c3ad 100644 --- a/benchmark/vm_thread_condvar1.rb +++ b/benchmark/vm_thread_condvar1.rb @@ -1,9 +1,9 @@ # two threads, two mutex, two condvar ping-pong require 'thread' -m1 = Mutex.new -m2 = Mutex.new -cv1 = ConditionVariable.new -cv2 = ConditionVariable.new +m1 = Thread::Mutex.new +m2 = Thread::Mutex.new +cv1 = Thread::ConditionVariable.new +cv2 = Thread::ConditionVariable.new max = 100000 i = 0 wait = nil diff --git a/benchmark/vm_thread_condvar2.rb b/benchmark/vm_thread_condvar2.rb index 7c8dc19481..6590c4134b 100644 --- a/benchmark/vm_thread_condvar2.rb +++ b/benchmark/vm_thread_condvar2.rb @@ -1,16 +1,16 @@ # many threads, one mutex, many condvars require 'thread' -m = Mutex.new -cv1 = ConditionVariable.new -cv2 = ConditionVariable.new +m = Thread::Mutex.new +cv1 = Thread::ConditionVariable.new +cv2 = Thread::ConditionVariable.new max = 1000 n = 100 waiting = 0 scvs = [] waiters = n.times.map do |i| - start_cv = ConditionVariable.new + start_cv = Thread::ConditionVariable.new scvs << start_cv - start_mtx = Mutex.new + start_mtx = Thread::Mutex.new start_mtx.synchronize do th = Thread.new(start_mtx, start_cv) do |sm, scv| m.synchronize do diff --git a/benchmark/vm_thread_pass.rb b/benchmark/vm_thread_pass.rb index b5b3c0bc85..438bd08d45 100644 --- a/benchmark/vm_thread_pass.rb +++ b/benchmark/vm_thread_pass.rb @@ -1,8 +1,8 @@ -# Plenty Thtread.pass +# Plenty Thread.pass # A performance may depend on GVL implementation. -tmax = (ARGV.shift || 2).to_i -lmax = 200_000 / tmax +tmax = (ARGV.shift || 8).to_i +lmax = 400_000 / tmax (1..tmax).map{ Thread.new{ diff --git a/benchmark/vm_thread_pass_flood.rb b/benchmark/vm_thread_pass_flood.rb index a660aafc18..65df8e6154 100644 --- a/benchmark/vm_thread_pass_flood.rb +++ b/benchmark/vm_thread_pass_flood.rb @@ -1,10 +1,10 @@ # n.b. this is a good test for GVL when pinned to a single CPU -1000.times{ +5_000.times{ Thread.new{loop{Thread.pass}} } i = 0 -while i<10000 +while i<10_000 i += 1 end diff --git a/benchmark/vm_thread_queue.rb b/benchmark/vm_thread_queue.rb index 274ceda366..1dd3696a3c 100644 --- a/benchmark/vm_thread_queue.rb +++ b/benchmark/vm_thread_queue.rb @@ -1,6 +1,6 @@ require 'thread' -n = 1_000_000 +n = 10_000_000 q = Thread::Queue.new consumer = Thread.new{ while q.pop diff --git a/benchmark/vm_thread_sleep.yml b/benchmark/vm_thread_sleep.yml new file mode 100644 index 0000000000..96901d8466 --- /dev/null +++ b/benchmark/vm_thread_sleep.yml @@ -0,0 +1,4 @@ +benchmark: + vm_thread_sleep: | + Thread.new { sleep } +loop_count: 10_000 diff --git a/benchmark/vm_unif1.yml b/benchmark/vm_unif1.yml new file mode 100644 index 0000000000..04187bb0e2 --- /dev/null +++ b/benchmark/vm_unif1.yml @@ -0,0 +1,7 @@ +prelude: | + def m a, b + end +benchmark: + vm_unif1: | + m 100, 200 +loop_count: 6000000 diff --git a/benchmark/vm_yield.yml b/benchmark/vm_yield.yml new file mode 100644 index 0000000000..230be3d84f --- /dev/null +++ b/benchmark/vm_yield.yml @@ -0,0 +1,13 @@ +# while loop cost is not removed due to benchmark_driver.gem's limitation +benchmark: + vm_yield: | + def m + i = 0 + while i<30_000_000 + i += 1 + yield + end + end + + m{} +loop_count: 1 diff --git a/benchmark/vm_zsuper.yml b/benchmark/vm_zsuper.yml new file mode 100644 index 0000000000..bfb5837578 --- /dev/null +++ b/benchmark/vm_zsuper.yml @@ -0,0 +1,18 @@ +prelude: | + class C + def m a + 1 + end + end + + class CC < C + def m a + super + end + end + + obj = CC.new +benchmark: + vm_zsuper: | + obj.m 10 +loop_count: 6000000 @@ -9,32 +9,45 @@ **********************************************************************/ -#include "internal.h" -#include "ruby/thread.h" -#include "ruby/util.h" -#include "id.h" +#include "ruby/internal/config.h" + +#include <ctype.h> +#include <float.h> +#include <math.h> #ifdef HAVE_STRINGS_H -#include <strings.h> +# include <strings.h> #endif -#include <math.h> -#include <float.h> -#include <ctype.h> + #ifdef HAVE_IEEEFP_H -#include <ieeefp.h> +# include <ieeefp.h> #endif -#include "ruby_assert.h" +#if !defined(USE_GMP) #if defined(HAVE_LIBGMP) && defined(HAVE_GMP_H) -#define USE_GMP -#include <gmp.h> +# define USE_GMP 1 +#else +# define USE_GMP 0 +#endif +#endif +#if USE_GMP +# include <gmp.h> #endif -#define RB_BIGNUM_TYPE_P(x) RB_TYPE_P((x), T_BIGNUM) +#include "id.h" +#include "internal.h" +#include "internal/bignum.h" +#include "internal/complex.h" +#include "internal/gc.h" +#include "internal/numeric.h" +#include "internal/object.h" +#include "internal/sanitizers.h" +#include "internal/variable.h" +#include "internal/warnings.h" +#include "ruby/thread.h" +#include "ruby/util.h" +#include "ruby_assert.h" -#ifndef RUBY_INTEGER_UNIFICATION -VALUE rb_cBignum; -#endif const char ruby_digitmap[] = "0123456789abcdefghijklmnopqrstuvwxyz"; #ifndef SIZEOF_BDIGIT_DBL @@ -65,7 +78,7 @@ STATIC_ASSERT(sizeof_long_and_sizeof_bdigit, SIZEOF_BDIGIT % SIZEOF_LONG == 0); #else # define HOST_BIGENDIAN_P 0 #endif -/* (!LSHIFTABLE(d, n) ? 0 : (n)) is same as n but suppress a warning, C4293, by Visual Studio. */ +/* (!LSHIFTABLE(d, n) ? 0 : (n)) is the same as n but suppress a warning, C4293, by Visual Studio. */ #define LSHIFTABLE(d, n) ((n) < sizeof(d) * CHAR_BIT) #define LSHIFTX(d, n) (!LSHIFTABLE(d, n) ? 0 : ((d) << (!LSHIFTABLE(d, n) ? 0 : (n)))) #define CLEAR_LOWBITS(d, numbits) ((d) & LSHIFTX(~((d)*0), (numbits))) @@ -92,8 +105,8 @@ STATIC_ASSERT(sizeof_long_and_sizeof_bdigit, SIZEOF_BDIGIT % SIZEOF_LONG == 0); #endif #define BIGZEROP(x) (BIGNUM_LEN(x) == 0 || \ - (BDIGITS(x)[0] == 0 && \ - (BIGNUM_LEN(x) == 1 || bigzero_p(x)))) + (BDIGITS(x)[0] == 0 && \ + (BIGNUM_LEN(x) == 1 || bigzero_p(x)))) #define BIGSIZE(x) (BIGNUM_LEN(x) == 0 ? (size_t)0 : \ BDIGITS(x)[BIGNUM_LEN(x)-1] ? \ (size_t)(BIGNUM_LEN(x)*SIZEOF_BDIGIT - nlz(BDIGITS(x)[BIGNUM_LEN(x)-1])/CHAR_BIT) : \ @@ -138,7 +151,7 @@ STATIC_ASSERT(sizeof_long_and_sizeof_bdigit, SIZEOF_BDIGIT % SIZEOF_LONG == 0); #define GMP_DIV_DIGITS 20 #define GMP_BIG2STR_DIGITS 20 #define GMP_STR2BIG_DIGITS 20 -#ifdef USE_GMP +#if USE_GMP # define NAIVE_MUL_DIGITS GMP_MUL_DIGITS #else # define NAIVE_MUL_DIGITS KARATSUBA_MUL_DIGITS @@ -149,15 +162,11 @@ typedef void (mulfunc_t)(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t xn, c static mulfunc_t bary_mul_toom3_start; static mulfunc_t bary_mul_karatsuba_start; static BDIGIT bigdivrem_single(BDIGIT *qds, const BDIGIT *xds, size_t xn, BDIGIT y); -static void bary_divmod(BDIGIT *qds, size_t qn, BDIGIT *rds, size_t rn, const BDIGIT *xds, size_t xn, const BDIGIT *yds, size_t yn); -static VALUE bigmul0(VALUE x, VALUE y); -static void bary_mul_toom3(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t xn, const BDIGIT *yds, size_t yn, BDIGIT *wds, size_t wn); static VALUE bignew_1(VALUE klass, size_t len, int sign); static inline VALUE bigtrunc(VALUE x); static VALUE bigsq(VALUE x); -static void bigdivmod(VALUE x, VALUE y, volatile VALUE *divp, volatile VALUE *modp); static inline VALUE power_cache_get_power(int base, int power_level, size_t *numdigits_ret); #if SIZEOF_BDIGIT <= SIZEOF_INT @@ -410,9 +419,9 @@ bary_small_lshift(BDIGIT *zds, const BDIGIT *xds, size_t n, int shift) assert(0 <= shift && shift < BITSPERDIG); for (i=0; i<n; i++) { - num = num | (BDIGIT_DBL)*xds++ << shift; - *zds++ = BIGLO(num); - num = BIGDN(num); + num = num | (BDIGIT_DBL)*xds++ << shift; + *zds++ = BIGLO(num); + num = BIGDN(num); } return BIGLO(num); } @@ -428,9 +437,9 @@ bary_small_rshift(BDIGIT *zds, const BDIGIT *xds, size_t n, int shift, BDIGIT hi num = BIGUP(higher_bdigit); for (i = 0; i < n; i++) { BDIGIT x = xds[n - i - 1]; - num = (num | x) >> shift; + num = (num | x) >> shift; zds[n - i - 1] = BIGLO(num); - num = BIGUP(x); + num = BIGUP(x); } } @@ -440,7 +449,7 @@ bary_zero_p(const BDIGIT *xds, size_t xn) if (xn == 0) return 1; do { - if (xds[--xn]) return 0; + if (xds[--xn]) return 0; } while (xn); return 1; } @@ -457,7 +466,6 @@ static int bary_2comp(BDIGIT *ds, size_t n) { size_t i; - i = 0; for (i = 0; i < n; i++) { if (ds[i] != 0) { goto non_zero; @@ -969,7 +977,7 @@ integer_unpack_num_bdigits_small(size_t numwords, size_t wordsize, size_t nails, { /* nlp_bits stands for number of leading padding bits */ size_t num_bits = (wordsize * CHAR_BIT - nails) * numwords; - size_t num_bdigits = (num_bits + BITSPERDIG - 1) / BITSPERDIG; + size_t num_bdigits = roomof(num_bits, BITSPERDIG); *nlp_bits_ret = (int)(num_bdigits * BITSPERDIG - num_bits); return num_bdigits; } @@ -979,7 +987,7 @@ integer_unpack_num_bdigits_generic(size_t numwords, size_t wordsize, size_t nail { /* BITSPERDIG = SIZEOF_BDIGIT * CHAR_BIT */ /* num_bits = (wordsize * CHAR_BIT - nails) * numwords */ - /* num_bdigits = (num_bits + BITSPERDIG - 1) / BITSPERDIG */ + /* num_bdigits = roomof(num_bits, BITSPERDIG) */ /* num_bits = CHAR_BIT * (wordsize * numwords) - nails * numwords = CHAR_BIT * num_bytes1 - nails * numwords */ size_t num_bytes1 = wordsize * numwords; @@ -1047,6 +1055,7 @@ integer_unpack_num_bdigits(size_t numwords, size_t wordsize, size_t nails, int * size_t num_bdigits1 = integer_unpack_num_bdigits_generic(numwords, wordsize, nails, &nlp_bits1); assert(num_bdigits == num_bdigits1); assert(*nlp_bits_ret == nlp_bits1); + (void)num_bdigits1; } #endif } @@ -1341,9 +1350,9 @@ bary_subb(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t xn, const BDIGIT *yd num = borrow ? -1 : 0; for (i = 0; i < sn; i++) { - num += (BDIGIT_DBL_SIGNED)xds[i] - yds[i]; - zds[i] = BIGLO(num); - num = BIGDN(num); + num += (BDIGIT_DBL_SIGNED)xds[i] - yds[i]; + zds[i] = BIGLO(num); + num = BIGDN(num); } if (yn <= xn) { for (; i < xn; i++) { @@ -1362,7 +1371,7 @@ bary_subb(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t xn, const BDIGIT *yd } if (num == 0) goto num_is_zero; for (; i < zn; i++) { - zds[i] = BDIGMAX; + zds[i] = BDIGMAX; } return 1; @@ -1370,10 +1379,10 @@ bary_subb(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t xn, const BDIGIT *yd if (xds == zds && xn == zn) return 0; for (; i < xn; i++) { - zds[i] = xds[i]; + zds[i] = xds[i]; } for (; i < zn; i++) { - zds[i] = 0; + zds[i] = 0; } return 0; } @@ -1400,27 +1409,27 @@ bary_addc(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t xn, const BDIGIT *yd assert(yn <= zn); if (xn > yn) { - const BDIGIT *tds; - tds = xds; xds = yds; yds = tds; - i = xn; xn = yn; yn = i; + const BDIGIT *tds; + tds = xds; xds = yds; yds = tds; + i = xn; xn = yn; yn = i; } num = carry ? 1 : 0; for (i = 0; i < xn; i++) { - num += (BDIGIT_DBL)xds[i] + yds[i]; - zds[i] = BIGLO(num); - num = BIGDN(num); + num += (BDIGIT_DBL)xds[i] + yds[i]; + zds[i] = BIGLO(num); + num = BIGDN(num); } for (; i < yn; i++) { if (num == 0) goto num_is_zero; - num += yds[i]; - zds[i] = BIGLO(num); - num = BIGDN(num); + num += yds[i]; + zds[i] = BIGLO(num); + num = BIGDN(num); } for (; i < zn; i++) { if (num == 0) goto num_is_zero; - zds[i] = BIGLO(num); - num = BIGDN(num); + zds[i] = BIGLO(num); + num = BIGDN(num); } return num != 0; @@ -1428,10 +1437,10 @@ bary_addc(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t xn, const BDIGIT *yd if (yds == zds && yn == zn) return 0; for (; i < yn; i++) { - zds[i] = yds[i]; + zds[i] = yds[i]; } for (; i < zn; i++) { - zds[i] = 0; + zds[i] = 0; } return 0; } @@ -1570,7 +1579,7 @@ rb_big_mul_normal(VALUE x, VALUE y) /* efficient squaring (2 times faster than normal multiplication) * ref: Handbook of Applied Cryptography, Algorithm 14.16 - * http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf + * https://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf */ static void bary_sq_fast(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t xn) @@ -1588,30 +1597,30 @@ bary_sq_fast(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t xn) return; for (i = 0; i < xn-1; i++) { - v = (BDIGIT_DBL)xds[i]; - if (!v) + v = (BDIGIT_DBL)xds[i]; + if (!v) continue; - c = (BDIGIT_DBL)zds[i + i] + v * v; - zds[i + i] = BIGLO(c); - c = BIGDN(c); - v *= 2; + c = (BDIGIT_DBL)zds[i + i] + v * v; + zds[i + i] = BIGLO(c); + c = BIGDN(c); + v *= 2; vl = BIGLO(v); vh = (int)BIGDN(v); - for (j = i + 1; j < xn; j++) { - w = (BDIGIT_DBL)xds[j]; - c += (BDIGIT_DBL)zds[i + j] + vl * w; - zds[i + j] = BIGLO(c); - c = BIGDN(c); - if (vh) + for (j = i + 1; j < xn; j++) { + w = (BDIGIT_DBL)xds[j]; + c += (BDIGIT_DBL)zds[i + j] + vl * w; + zds[i + j] = BIGLO(c); + c = BIGDN(c); + if (vh) c += w; - } - if (c) { - c += (BDIGIT_DBL)zds[i + xn]; - zds[i + xn] = BIGLO(c); - c = BIGDN(c); + } + if (c) { + c += (BDIGIT_DBL)zds[i + xn]; + zds[i + xn] = BIGLO(c); + c = BIGDN(c); if (c) zds[i + xn + 1] += (BDIGIT)c; - } + } } /* i == xn-1 */ @@ -1636,13 +1645,21 @@ rb_big_sq_fast(VALUE x) return z; } +static inline size_t +max_size(size_t a, size_t b) +{ + return (a > b ? a : b); +} + /* balancing multiplication by slicing larger argument */ static void -bary_mul_balance_with_mulfunc(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t xn, const BDIGIT *yds, size_t yn, BDIGIT *wds, size_t wn, mulfunc_t *mulfunc) +bary_mul_balance_with_mulfunc(BDIGIT *const zds, const size_t zn, + const BDIGIT *const xds, const size_t xn, + const BDIGIT *const yds, const size_t yn, + BDIGIT *wds, size_t wn, mulfunc_t *const mulfunc) { VALUE work = 0; - size_t yn0 = yn; - size_t r, n; + size_t n; assert(xn + yn <= zn); assert(xn <= yn); @@ -1650,14 +1667,26 @@ bary_mul_balance_with_mulfunc(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t BDIGITS_ZERO(zds, xn); + if (wn < xn) { + /* The condition when a new buffer is needed: + * 1. (2(xn+r) > zn-(yn-r)) => (2xn+r > zn-yn), at the last + * iteration (or r == 0) + * 2. (2(xn+xn) > zn-(yn-r-xn)) => (3xn-r > zn-yn), at the + * previous iteration. + */ + const size_t r = yn % xn; + if (2*xn + yn + max_size(xn-r, r) > zn) { + wn = xn; + wds = ALLOCV_N(BDIGIT, work, wn); + } + } + n = 0; - while (yn > 0) { - BDIGIT *tds; - size_t tn; - r = xn > yn ? yn : xn; - tn = xn + r; + while (yn > n) { + const size_t r = (xn > (yn - n) ? (yn - n) : xn); + const size_t tn = (xn + r); if (2 * (xn + r) <= zn - n) { - tds = zds + n + xn + r; + BDIGIT *const tds = zds + n + xn + r; mulfunc(tds, tn, xds, xn, yds + n, r, wds, wn); BDIGITS_ZERO(zds + n + xn, r); bary_add(zds + n, tn, @@ -1665,21 +1694,25 @@ bary_mul_balance_with_mulfunc(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t tds, tn); } else { + BDIGIT *const tds = zds + n; if (wn < xn) { + /* xn is invariant, only once here */ +#if 0 wn = xn; wds = ALLOCV_N(BDIGIT, work, wn); +#else + rb_bug("wds is not enough: %" PRIdSIZE " for %" PRIdSIZE, wn, xn); +#endif } - tds = zds + n; MEMCPY(wds, zds + n, BDIGIT, xn); mulfunc(tds, tn, xds, xn, yds + n, r, wds+xn, wn-xn); bary_add(zds + n, tn, zds + n, tn, wds, xn); } - yn -= r; - n += r; + n += r; } - BDIGITS_ZERO(zds+xn+yn0, zn - (xn+yn0)); + BDIGITS_ZERO(zds+xn+yn, zn - (xn+yn)); if (work) ALLOCV_END(work); @@ -1996,7 +2029,7 @@ bary_mul_toom3(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t xn, const BDIGI } /* - * ref. http://en.wikipedia.org/wiki/Toom%E2%80%93Cook_multiplication + * ref. https://en.wikipedia.org/wiki/Toom%E2%80%93Cook_multiplication * * x(b) = x0 * b^0 + x1 * b^1 + x2 * b^2 * y(b) = y0 * b^0 + y1 * b^1 + y2 * b^2 @@ -2069,21 +2102,21 @@ bary_mul_toom3(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t xn, const BDIGI v3n = u3n; v3ds = u3ds; v3p = u3p; } else { - /* v1 <- y0 + y2 */ + /* v1 <- y0 + y2 */ bary_add(v1ds, v1n, y0ds, y0n, y2ds, y2n); v1p = 1; - /* y(-1) : v2 <- v1 - y1 = y0 - y1 + y2 */ + /* y(-1) : v2 <- v1 - y1 = y0 - y1 + y2 */ v2p = 1; if (bary_sub(v2ds, v2n, v1ds, v1n, y1ds, y1n)) { bary_2comp(v2ds, v2n); v2p = 0; } - /* y(1) : v1 <- v1 + y1 = y0 + y1 + y2 */ + /* y(1) : v1 <- v1 + y1 = y0 + y1 + y2 */ bary_add(v1ds, v1n, v1ds, v1n, y1ds, y1n); - /* y(-2) : v3 <- 2 * (v2 + y2) - y0 = y0 - 2 * (y1 - 2 * y2) */ + /* y(-2) : v3 <- 2 * (v2 + y2) - y0 = y0 - 2 * (y1 - 2 * y2) */ v3p = 1; if (v2p) { bary_add(v3ds, v3n, v2ds, v2n, y2ds, y2n); @@ -2276,11 +2309,24 @@ rb_big_mul_toom3(VALUE x, VALUE y) return z; } -#ifdef USE_GMP +#if USE_GMP +static inline void +bdigits_to_mpz(mpz_t mp, const BDIGIT *digits, size_t len) +{ + const size_t nails = (sizeof(BDIGIT)-SIZEOF_BDIGIT)*CHAR_BIT; + mpz_import(mp, len, -1, sizeof(BDIGIT), 0, nails, digits); +} + +static inline void +bdigits_from_mpz(mpz_t mp, BDIGIT *digits, size_t *len) +{ + const size_t nails = (sizeof(BDIGIT)-SIZEOF_BDIGIT)*CHAR_BIT; + mpz_export(digits, len, -1, sizeof(BDIGIT), 0, nails, mp); +} + static void bary_mul_gmp(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t xn, const BDIGIT *yds, size_t yn) { - const size_t nails = (sizeof(BDIGIT)-SIZEOF_BDIGIT)*CHAR_BIT; mpz_t x, y, z; size_t count; @@ -2289,15 +2335,15 @@ bary_mul_gmp(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t xn, const BDIGIT mpz_init(x); mpz_init(y); mpz_init(z); - mpz_import(x, xn, -1, sizeof(BDIGIT), 0, nails, xds); + bdigits_to_mpz(x, xds, xn); if (xds == yds && xn == yn) { mpz_mul(z, x, x); } else { - mpz_import(y, yn, -1, sizeof(BDIGIT), 0, nails, yds); + bdigits_to_mpz(y, yds, yn); mpz_mul(z, x, y); } - mpz_export(zds, &count, -1, sizeof(BDIGIT), 0, nails, z); + bdigits_from_mpz(z, zds, &count); BDIGITS_ZERO(zds+count, zn-count); mpz_clear(x); mpz_clear(y); @@ -2336,9 +2382,9 @@ bary_sparse_p(const BDIGIT *ds, size_t n) { long c = 0; - if ( ds[rb_genrand_ulong_limited(n / 2) + n / 4]) c++; - if (c <= 1 && ds[rb_genrand_ulong_limited(n / 2) + n / 4]) c++; - if (c <= 1 && ds[rb_genrand_ulong_limited(n / 2) + n / 4]) c++; + if ( ds[2 * n / 5]) c++; + if (c <= 1 && ds[ n / 2]) c++; + if (c <= 1 && ds[3 * n / 5]) c++; return (c <= 1) ? 1 : 0; } @@ -2401,8 +2447,8 @@ bary_mul_precheck(BDIGIT **zdsp, size_t *znp, const BDIGIT **xdsp, size_t *xnp, if (xn > yn) { const BDIGIT *tds; size_t tn; - tds = xds; xds = yds; yds = tds; - tn = xn; xn = yn; yn = tn; + tds = xds; xds = yds; yds = tds; + tn = xn; xn = yn; yn = tn; } assert(xn <= yn); @@ -2446,12 +2492,7 @@ bary_mul_karatsuba_branch(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t xn, { /* normal multiplication when x is small */ if (xn < KARATSUBA_MUL_DIGITS) { - normal: - if (xds == yds && xn == yn) - bary_sq_fast(zds, zn, xds, xn); - else - bary_short_mul(zds, zn, xds, xn, yds, yn); - return; + goto normal; } /* normal multiplication when x or y is a sparse bignum */ @@ -2469,6 +2510,15 @@ bary_mul_karatsuba_branch(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t xn, /* multiplication by karatsuba method */ bary_mul_karatsuba(zds, zn, xds, xn, yds, yn, wds, wn); + return; + + normal: + if (xds == yds && xn == yn) { + bary_sq_fast(zds, zn, xds, xn); + } + else { + bary_short_mul(zds, zn, xds, xn, yds, yn); + } } static void @@ -2524,7 +2574,7 @@ bary_mul(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t xn, const BDIGIT *yds } } -#ifdef USE_GMP +#if USE_GMP bary_mul_gmp(zds, zn, xds, xn, yds, yn); #else bary_mul_toom3_start(zds, zn, xds, xn, yds, yn, NULL, 0); @@ -2548,30 +2598,31 @@ bigdivrem1(void *ptr) BDIGIT q; do { - if (bds->stop) { - bds->zn = zn; - return 0; + if (bds->stop) { + bds->zn = zn; + return 0; } - if (zds[zn-1] == yds[yn-1]) q = BDIGMAX; - else q = (BDIGIT)((BIGUP(zds[zn-1]) + zds[zn-2])/yds[yn-1]); - if (q) { + if (zds[zn-1] == yds[yn-1]) q = BDIGMAX; + else q = (BDIGIT)((BIGUP(zds[zn-1]) + zds[zn-2])/yds[yn-1]); + if (q) { num = bigdivrem_mulsub(zds+zn-(yn+1), yn+1, q, yds, yn); - while (num) { /* "add back" required */ - q--; + while (num) { /* "add back" required */ + q--; num = bary_add(zds+zn-(yn+1), yn, zds+zn-(yn+1), yn, yds, yn); num--; - } - } + } + } zn--; - zds[zn] = q; + zds[zn] = q; } while (zn > yn); return 0; } +/* async-signal-safe */ static void rb_big_stop(void *ptr) { @@ -2635,16 +2686,16 @@ bigdivrem_restoring(BDIGIT *zds, size_t zn, BDIGIT *yds, size_t yn) bds.zn = zn - ynzero; if (bds.zn > 10000 || bds.yn > 10000) { retry: - bds.stop = Qfalse; - rb_thread_call_without_gvl(bigdivrem1, &bds, rb_big_stop, &bds); + bds.stop = Qfalse; + rb_nogvl(bigdivrem1, &bds, rb_big_stop, &bds, RB_NOGVL_UBF_ASYNC_SAFE); - if (bds.stop == Qtrue) { - /* execute trap handler, but exception was not raised. */ - goto retry; - } + if (bds.stop == Qtrue) { + /* execute trap handler, but exception was not raised. */ + goto retry; + } } else { - bigdivrem1(&bds); + bigdivrem1(&bds); } } @@ -2743,11 +2794,10 @@ rb_big_divrem_normal(VALUE x, VALUE y) return rb_assoc_new(q, r); } -#ifdef USE_GMP +#if USE_GMP static void bary_divmod_gmp(BDIGIT *qds, size_t qn, BDIGIT *rds, size_t rn, const BDIGIT *xds, size_t xn, const BDIGIT *yds, size_t yn) { - const size_t nails = (sizeof(BDIGIT)-SIZEOF_BDIGIT)*CHAR_BIT; mpz_t x, y, q, r; size_t count; @@ -2761,8 +2811,8 @@ bary_divmod_gmp(BDIGIT *qds, size_t qn, BDIGIT *rds, size_t rn, const BDIGIT *xd if (qds) mpz_init(q); if (rds) mpz_init(r); - mpz_import(x, xn, -1, sizeof(BDIGIT), 0, nails, xds); - mpz_import(y, yn, -1, sizeof(BDIGIT), 0, nails, yds); + bdigits_to_mpz(x, xds, xn); + bdigits_to_mpz(y, yds, yn); if (!rds) { mpz_fdiv_q(q, x, y); @@ -2778,13 +2828,13 @@ bary_divmod_gmp(BDIGIT *qds, size_t qn, BDIGIT *rds, size_t rn, const BDIGIT *xd mpz_clear(y); if (qds) { - mpz_export(qds, &count, -1, sizeof(BDIGIT), 0, nails, q); + bdigits_from_mpz(q, qds, &count); BDIGITS_ZERO(qds+count, qn-count); mpz_clear(q); } if (rds) { - mpz_export(rds, &count, -1, sizeof(BDIGIT), 0, nails, r); + bdigits_from_mpz(r, rds, &count); BDIGITS_ZERO(rds+count, rn-count); mpz_clear(r); } @@ -2828,7 +2878,7 @@ rb_big_divrem_gmp(VALUE x, VALUE y) static void bary_divmod_branch(BDIGIT *qds, size_t qn, BDIGIT *rds, size_t rn, const BDIGIT *xds, size_t xn, const BDIGIT *yds, size_t yn) { -#ifdef USE_GMP +#if USE_GMP if (GMP_DIV_DIGITS < xn) { bary_divmod_gmp(qds, qn, rds, rn, xds, xn, yds, yn); return; @@ -2883,29 +2933,8 @@ bary_divmod(BDIGIT *qds, size_t qn, BDIGIT *rds, size_t rn, const BDIGIT *xds, s } -#define BIGNUM_DEBUG 0 -#if BIGNUM_DEBUG -#define ON_DEBUG(x) do { x; } while (0) -static void -dump_bignum(VALUE x) -{ - long i; - printf("%c0x0", BIGNUM_SIGN(x) ? '+' : '-'); - for (i = BIGNUM_LEN(x); i--; ) { - printf("_%0*"PRIxBDIGIT, SIZEOF_BDIGIT*2, BDIGITS(x)[i]); - } - printf(", len=%"PRIuSIZE, BIGNUM_LEN(x)); - puts(""); -} - -static VALUE -rb_big_dump(VALUE x) -{ - dump_bignum(x); - return x; -} -#else -#define ON_DEBUG(x) +#ifndef BIGNUM_DEBUG +# define BIGNUM_DEBUG (0+RUBY_DEBUG) #endif static int @@ -2924,7 +2953,7 @@ int rb_cmpint(VALUE val, VALUE a, VALUE b) { if (NIL_P(val)) { - rb_cmperr(a, b); + rb_cmperr(a, b); } if (FIXNUM_P(val)) { long l = FIX2LONG(val); @@ -2933,9 +2962,9 @@ rb_cmpint(VALUE val, VALUE a, VALUE b) return 0; } if (RB_BIGNUM_TYPE_P(val)) { - if (BIGZEROP(val)) return 0; - if (BIGNUM_SIGN(val)) return 1; - return -1; + if (BIGZEROP(val)) return 0; + if (BIGNUM_SIGN(val)) return 1; + return -1; } if (RTEST(rb_funcall(val, '>', 1, INT2FIX(0)))) return 1; if (RTEST(rb_funcall(val, '<', 1, INT2FIX(0)))) return -1; @@ -2943,44 +2972,44 @@ rb_cmpint(VALUE val, VALUE a, VALUE b) } #define BIGNUM_SET_LEN(b,l) \ - ((RBASIC(b)->flags & BIGNUM_EMBED_FLAG) ? \ + (BIGNUM_EMBED_P(b) ? \ (void)(RBASIC(b)->flags = \ - (RBASIC(b)->flags & ~BIGNUM_EMBED_LEN_MASK) | \ - ((l) << BIGNUM_EMBED_LEN_SHIFT)) : \ + (RBASIC(b)->flags & ~BIGNUM_EMBED_LEN_MASK) | \ + ((l) << BIGNUM_EMBED_LEN_SHIFT)) : \ (void)(RBIGNUM(b)->as.heap.len = (l))) static void rb_big_realloc(VALUE big, size_t len) { BDIGIT *ds; - if (RBASIC(big)->flags & BIGNUM_EMBED_FLAG) { - if (BIGNUM_EMBED_LEN_MAX < len) { - ds = ALLOC_N(BDIGIT, len); - MEMCPY(ds, RBIGNUM(big)->as.ary, BDIGIT, BIGNUM_EMBED_LEN_MAX); - RBIGNUM(big)->as.heap.len = BIGNUM_LEN(big); - RBIGNUM(big)->as.heap.digits = ds; - RBASIC(big)->flags &= ~BIGNUM_EMBED_FLAG; - } + if (BIGNUM_EMBED_P(big)) { + if (BIGNUM_EMBED_LEN_MAX < len) { + ds = ALLOC_N(BDIGIT, len); + MEMCPY(ds, RBIGNUM(big)->as.ary, BDIGIT, BIGNUM_EMBED_LEN_MAX); + RBIGNUM(big)->as.heap.len = BIGNUM_LEN(big); + RBIGNUM(big)->as.heap.digits = ds; + FL_UNSET_RAW(big, BIGNUM_EMBED_FLAG); + } } else { - if (len <= BIGNUM_EMBED_LEN_MAX) { - ds = RBIGNUM(big)->as.heap.digits; - RBASIC(big)->flags |= BIGNUM_EMBED_FLAG; - BIGNUM_SET_LEN(big, len); + if (len <= BIGNUM_EMBED_LEN_MAX) { + ds = RBIGNUM(big)->as.heap.digits; + FL_SET_RAW(big, BIGNUM_EMBED_FLAG); + BIGNUM_SET_LEN(big, len); (void)VALGRIND_MAKE_MEM_UNDEFINED((void*)RBIGNUM(big)->as.ary, sizeof(RBIGNUM(big)->as.ary)); - if (ds) { - MEMCPY(RBIGNUM(big)->as.ary, ds, BDIGIT, len); - xfree(ds); - } - } - else { - if (BIGNUM_LEN(big) == 0) { - RBIGNUM(big)->as.heap.digits = ALLOC_N(BDIGIT, len); - } - else { - REALLOC_N(RBIGNUM(big)->as.heap.digits, BDIGIT, len); - } - } + if (ds) { + MEMCPY(RBIGNUM(big)->as.ary, ds, BDIGIT, len); + xfree(ds); + } + } + else { + if (BIGNUM_LEN(big) == 0) { + RBIGNUM(big)->as.heap.digits = ALLOC_N(BDIGIT, len); + } + else { + REALLOC_N(RBIGNUM(big)->as.heap.digits, BDIGIT, len); + } + } } } @@ -2995,18 +3024,19 @@ static VALUE bignew_1(VALUE klass, size_t len, int sign) { NEWOBJ_OF(big, struct RBignum, klass, T_BIGNUM | (RGENGC_WB_PROTECTED_BIGNUM ? FL_WB_PROTECTED : 0)); - BIGNUM_SET_SIGN(big, sign); + VALUE bigv = (VALUE)big; + BIGNUM_SET_SIGN(bigv, sign); if (len <= BIGNUM_EMBED_LEN_MAX) { - RBASIC(big)->flags |= BIGNUM_EMBED_FLAG; - BIGNUM_SET_LEN(big, len); - (void)VALGRIND_MAKE_MEM_UNDEFINED((void*)RBIGNUM(big)->as.ary, sizeof(RBIGNUM(big)->as.ary)); + FL_SET_RAW(bigv, BIGNUM_EMBED_FLAG); + BIGNUM_SET_LEN(bigv, len); + (void)VALGRIND_MAKE_MEM_UNDEFINED((void*)big->as.ary, sizeof(big->as.ary)); } else { - RBIGNUM(big)->as.heap.digits = ALLOC_N(BDIGIT, len); - RBIGNUM(big)->as.heap.len = len; + big->as.heap.digits = ALLOC_N(BDIGIT, len); + big->as.heap.len = len; } - OBJ_FREEZE(big); - return (VALUE)big; + OBJ_FREEZE(bigv); + return bigv; } VALUE @@ -3065,7 +3095,7 @@ abs2twocomp(VALUE *xp, long *n_ret) MEMCPY(BDIGITS(z), ds, BDIGIT, n); bary_2comp(BDIGITS(z), n); hibits = BDIGMAX; - *xp = z; + *xp = z; } *n_ret = n; return hibits; @@ -3089,7 +3119,7 @@ bigtrunc(VALUE x) if (len == 0) return x; while (--len && !ds[len]); if (BIGNUM_LEN(x) > len+1) { - rb_big_resize(x, len+1); + rb_big_resize(x, len+1); } return x; } @@ -3142,7 +3172,7 @@ static VALUE bignorm(VALUE x) { if (RB_BIGNUM_TYPE_P(x)) { - x = bigfixize(x); + x = bigfixize(x); } return x; } @@ -3164,8 +3194,8 @@ rb_uint2big(uintptr_t n) digits[0] = n; #else for (i = 0; i < bdigit_roomof(SIZEOF_VALUE); i++) { - digits[i] = BIGLO(n); - n = BIGDN(n); + digits[i] = BIGLO(n); + n = BIGDN(n); } #endif @@ -3184,14 +3214,14 @@ rb_int2big(intptr_t n) if (n < 0) { u = 1 + (VALUE)(-(n + 1)); /* u = -n avoiding overflow */ - neg = 1; + neg = 1; } else { u = n; } big = rb_uint2big(u); if (neg) { - BIGNUM_SET_NEGATIVE_SIGN(big); + BIGNUM_SET_NEGATIVE_SIGN(big); } return big; } @@ -3350,7 +3380,7 @@ absint_numwords_generic(size_t numbytes, int nlz_bits_in_msbyte, size_t word_num if (sign == 2) { #if defined __GNUC__ && (__GNUC__ == 4 && __GNUC_MINOR__ == 4) - *nlz_bits_ret = 0; + *nlz_bits_ret = 0; #endif return (size_t)-1; } @@ -3383,7 +3413,7 @@ rb_absint_numwords(VALUE val, size_t word_numbits, size_t *nlz_bits_ret) size_t numbytes; int nlz_bits_in_msbyte; size_t numwords; - size_t nlz_bits; + size_t nlz_bits = 0; if (word_numbits == 0) return (size_t)-1; @@ -3398,6 +3428,7 @@ rb_absint_numwords(VALUE val, size_t word_numbits, size_t *nlz_bits_ret) numwords0 = absint_numwords_generic(numbytes, nlz_bits_in_msbyte, word_numbits, &nlz_bits0); assert(numwords0 == numwords); assert(nlz_bits0 == nlz_bits); + (void)numwords0; } #endif } @@ -3585,7 +3616,7 @@ rb_integer_pack(VALUE val, void *words, size_t numwords, size_t wordsize, size_t } /* - * Import an integer into a buffer. + * Import an integer from a buffer. * * [words] buffer to import. * [numwords] the size of given buffer as number of words. @@ -3669,7 +3700,7 @@ rb_integer_unpack(const void *words, size_t numwords, size_t wordsize, size_t na } else if (num_bdigits == numberof(fixbuf)) { val = bignew((long)num_bdigits+1, 0); - MEMCPY(BDIGITS(val), fixbuf, BDIGIT, num_bdigits); + MEMCPY(BDIGITS(val), fixbuf, BDIGIT, num_bdigits); BDIGITS(val)[num_bdigits++] = 1; } else { @@ -3681,11 +3712,11 @@ rb_integer_unpack(const void *words, size_t numwords, size_t wordsize, size_t na BDIGIT_DBL u = fixbuf[0] + BIGUP(fixbuf[1]); if (u == 0) return LONG2FIX(0); - if (0 < sign && POSFIXABLE(u)) - return LONG2FIX(u); - if (sign < 0 && BDIGIT_MSB(fixbuf[1]) == 0 && + if (0 < sign && POSFIXABLE(u)) + return LONG2FIX((long)u); + if (sign < 0 && BDIGIT_MSB(fixbuf[1]) == 0 && NEGFIXABLE(-(BDIGIT_DBL_SIGNED)u)) - return LONG2FIX(-(BDIGIT_DBL_SIGNED)u); + return LONG2FIX((long)-(BDIGIT_DBL_SIGNED)u); val = bignew((long)num_bdigits, 0 <= sign); MEMCPY(BDIGITS(val), fixbuf, BDIGIT, num_bdigits); } @@ -3735,42 +3766,41 @@ str2big_scan_digits(const char *s, const char *str, int base, int badcheck, size int c; if (!len) { - *num_digits_p = 0; - *len_p = 0; - return TRUE; + *num_digits_p = 0; + *len_p = 0; + return TRUE; } - if (badcheck && *str == '_') goto bad; + if (badcheck && *str == '_') return FALSE; while ((c = *str++) != 0) { - if (c == '_') { - if (nondigit) { - if (badcheck) goto bad; - break; - } - nondigit = (char) c; - } - else if ((c = conv_digit(c)) < 0 || c >= base) { - break; - } - else { - nondigit = 0; - num_digits++; - digits_end = str; - } - if (len > 0 && !--len) break; - } - if (badcheck && nondigit) goto bad; + if (c == '_') { + if (nondigit) { + if (badcheck) return FALSE; + break; + } + nondigit = (char) c; + } + else if ((c = conv_digit(c)) < 0 || c >= base) { + break; + } + else { + nondigit = 0; + num_digits++; + digits_end = str; + } + if (len > 0 && !--len) break; + } + if (badcheck && nondigit) return FALSE; if (badcheck && len) { - str--; - while (*str && ISSPACE(*str)) { - str++; - if (len > 0 && !--len) break; - } - if (len && *str) { - bad: - return FALSE; - } + str--; + while (*str && ISSPACE(*str)) { + str++; + if (len > 0 && !--len) break; + } + if (len && *str) { + return FALSE; + } } *num_digits_p = num_digits; *len_p = digits_end - digits_start; @@ -3945,7 +3975,7 @@ str2big_karatsuba( return z; } -#ifdef USE_GMP +#if USE_GMP static VALUE str2big_gmp( int sign, @@ -3955,7 +3985,6 @@ str2big_gmp( size_t num_bdigits, int base) { - const size_t nails = (sizeof(BDIGIT)-SIZEOF_BDIGIT)*CHAR_BIT; char *buf, *p; const char *q; VALUE tmps; @@ -3978,7 +4007,7 @@ str2big_gmp( zn = num_bdigits; z = bignew(zn, sign); zds = BDIGITS(z); - mpz_export(BDIGITS(z), &count, -1, sizeof(BDIGIT), 0, nails, mz); + bdigits_from_mpz(mz, BDIGITS(z), &count); BDIGITS_ZERO(zds+count, zn-count); mpz_clear(mz); @@ -3989,6 +4018,8 @@ str2big_gmp( } #endif +static VALUE rb_cstr_parse_inum(const char *str, ssize_t len, char **endp, int base); + /* * Parse +str+ as Ruby Integer, i.e., underscores, 0d and 0b prefixes. * @@ -4011,8 +4042,8 @@ rb_cstr_to_inum(const char *str, int base, int badcheck) char *end; VALUE ret = rb_cstr_parse_inum(str, -1, (badcheck ? NULL : &end), base); if (NIL_P(ret)) { - if (badcheck) rb_invalid_str(str, "Integer()"); - ret = INT2FIX(0); + if (badcheck) rb_invalid_str(str, "Integer()"); + ret = INT2FIX(0); } return ret; } @@ -4036,7 +4067,7 @@ rb_cstr_to_inum(const char *str, int base, int badcheck) VALUE rb_int_parse_cstr(const char *str, ssize_t len, char **endp, size_t *ndigits, - int base, int flags) + int base, int flags) { const char *const s = str; char sign = 1; @@ -4053,85 +4084,82 @@ rb_int_parse_cstr(const char *str, ssize_t len, char **endp, size_t *ndigits, const int badcheck = !endp; #define ADV(n) do {\ - if (len > 0 && len <= (n)) goto bad; \ - str += (n); \ - len -= (n); \ + if (len > 0 && len <= (n)) goto bad; \ + str += (n); \ + len -= (n); \ } while (0) #define ASSERT_LEN() do {\ - assert(len != 0); \ - if (len0 >= 0) assert(s + len0 == str + len); \ + assert(len != 0); \ + if (len0 >= 0) assert(s + len0 == str + len); \ } while (0) if (!str) { - bad: - if (endp) *endp = (char *)str; - if (ndigits) *ndigits = num_digits; - return z; + goto bad; } if (len && (flags & RB_INT_PARSE_SIGN)) { - while (ISSPACE(*str)) ADV(1); + while (ISSPACE(*str)) ADV(1); - if (str[0] == '+') { - ADV(1); - } - else if (str[0] == '-') { - ADV(1); - sign = 0; - } - ASSERT_LEN(); + if (str[0] == '+') { + ADV(1); + } + else if (str[0] == '-') { + ADV(1); + sign = 0; + } + ASSERT_LEN(); } if (base <= 0) { - if (str[0] == '0' && len > 1) { - switch (str[1]) { - case 'x': case 'X': - base = 16; - ADV(2); - break; - case 'b': case 'B': - base = 2; - ADV(2); - break; - case 'o': case 'O': - base = 8; - ADV(2); - break; - case 'd': case 'D': - base = 10; - ADV(2); - break; - default: - base = 8; - } - } - else if (base < -1) { - base = -base; - } - else { - base = 10; - } + if (str[0] == '0' && len > 1) { + switch (str[1]) { + case 'x': case 'X': + base = 16; + ADV(2); + break; + case 'b': case 'B': + base = 2; + ADV(2); + break; + case 'o': case 'O': + base = 8; + ADV(2); + break; + case 'd': case 'D': + base = 10; + ADV(2); + break; + default: + base = 8; + } + } + else if (base < -1) { + base = -base; + } + else { + base = 10; + } } else if (len == 1 || !(flags & RB_INT_PARSE_PREFIX)) { - /* no prefix */ + /* no prefix */ } else if (base == 2) { - if (str[0] == '0' && (str[1] == 'b'||str[1] == 'B')) { - ADV(2); - } + if (str[0] == '0' && (str[1] == 'b'||str[1] == 'B')) { + ADV(2); + } } else if (base == 8) { - if (str[0] == '0' && (str[1] == 'o'||str[1] == 'O')) { - ADV(2); - } + if (str[0] == '0' && (str[1] == 'o'||str[1] == 'O')) { + ADV(2); + } } else if (base == 10) { - if (str[0] == '0' && (str[1] == 'd'||str[1] == 'D')) { - ADV(2); - } + if (str[0] == '0' && (str[1] == 'd'||str[1] == 'D')) { + ADV(2); + } } else if (base == 16) { - if (str[0] == '0' && (str[1] == 'x'||str[1] == 'X')) { - ADV(2); - } + if (str[0] == '0' && (str[1] == 'x'||str[1] == 'X')) { + ADV(2); + } } if (!valid_radix_p(base)) { invalid_radix(base); @@ -4139,80 +4167,79 @@ rb_int_parse_cstr(const char *str, ssize_t len, char **endp, size_t *ndigits, if (!len) goto bad; num_digits = str - s; if (*str == '0' && len != 1) { /* squeeze preceding 0s */ - int us = 0; - const char *end = len < 0 ? NULL : str + len; - ++num_digits; - while ((c = *++str) == '0' || - ((flags & RB_INT_PARSE_UNDERSCORE) && c == '_')) { - if (c == '_') { - if (++us >= 2) - break; - } - else { - ++num_digits; - us = 0; - } - if (str == end) break; - } - if (!c || ISSPACE(c)) --str; - if (end) len = end - str; - ASSERT_LEN(); + int us = 0; + const char *end = len < 0 ? NULL : str + len; + ++num_digits; + while ((c = *++str) == '0' || + ((flags & RB_INT_PARSE_UNDERSCORE) && c == '_')) { + if (c == '_') { + if (++us >= 2) + break; + } + else { + ++num_digits; + us = 0; + } + if (str == end) break; + } + if (!c || ISSPACE(c)) --str; + if (end) len = end - str; } c = *str; c = conv_digit(c); if (c < 0 || c >= base) { - if (!badcheck && num_digits) z = INT2FIX(0); - goto bad; + if (!badcheck && num_digits) z = INT2FIX(0); + goto bad; } if (ndigits) *ndigits = num_digits; val = ruby_scan_digits(str, len, base, &num_digits, &ov); if (!ov) { - const char *end = &str[num_digits]; - if (num_digits > 0 && *end == '_' && (flags & RB_INT_PARSE_UNDERSCORE)) - goto bigparse; - if (endp) *endp = (char *)end; - if (ndigits) *ndigits += num_digits; - if (badcheck) { - if (num_digits == 0) return Qnil; /* no number */ - while (len < 0 ? *end : end < str + len) { - if (!ISSPACE(*end)) return Qnil; /* trailing garbage */ - end++; - } - } - - if (POSFIXABLE(val)) { - if (sign) return LONG2FIX(val); - else { - long result = -(long)val; - return LONG2FIX(result); - } - } - else { - VALUE big = rb_uint2big(val); - BIGNUM_SET_SIGN(big, sign); - return bignorm(big); - } + const char *end = &str[num_digits]; + if (num_digits > 0 && *end == '_' && (flags & RB_INT_PARSE_UNDERSCORE)) + goto bigparse; + if (endp) *endp = (char *)end; + if (ndigits) *ndigits += num_digits; + if (badcheck) { + if (num_digits == 0) return Qnil; /* no number */ + while (len < 0 ? *end : end < str + len) { + if (!ISSPACE(*end)) return Qnil; /* trailing garbage */ + end++; + } + } + + if (POSFIXABLE(val)) { + if (sign) return LONG2FIX(val); + else { + long result = -(long)val; + return LONG2FIX(result); + } + } + else { + VALUE big = rb_uint2big(val); + BIGNUM_SET_SIGN(big, sign); + return bignorm(big); + } } bigparse: digits_start = str; if (!str2big_scan_digits(s, str, base, badcheck, &num_digits, &len)) - goto bad; + goto bad; if (endp) *endp = (char *)(str + len); if (ndigits) *ndigits += num_digits; digits_end = digits_start + len; if (POW2_P(base)) { z = str2big_poweroftwo(sign, digits_start, digits_end, num_digits, - bit_length(base-1)); + bit_length(base-1)); } else { int digits_per_bdigits_dbl; maxpow_in_bdigit_dbl(base, &digits_per_bdigits_dbl); num_bdigits = roomof(num_digits, digits_per_bdigits_dbl)*2; -#ifdef USE_GMP +#if USE_GMP if (GMP_STR2BIG_DIGITS < num_bdigits) { z = str2big_gmp(sign, digits_start, digits_end, num_digits, num_bdigits, base); @@ -4230,13 +4257,18 @@ rb_int_parse_cstr(const char *str, ssize_t len, char **endp, size_t *ndigits, } return bignorm(z); + + bad: + if (endp) *endp = (char *)str; + if (ndigits) *ndigits = num_digits; + return z; } -VALUE +static VALUE rb_cstr_parse_inum(const char *str, ssize_t len, char **endp, int base) { return rb_int_parse_cstr(str, len, endp, NULL, base, - RB_INT_PARSE_DEFAULT); + RB_INT_PARSE_DEFAULT); } VALUE @@ -4285,14 +4317,14 @@ rb_str2big_poweroftwo(VALUE arg, int base, int badcheck) s = str = StringValueCStr(arg); len = RSTRING_LEN(arg); if (*str == '-') { - len--; + len--; str++; positive_p = 0; } digits_start = str; if (!str2big_scan_digits(s, str, base, badcheck, &num_digits, &len)) - invalid_integer(arg); + invalid_integer(arg); digits_end = digits_start + len; z = str2big_poweroftwo(positive_p, digits_start, digits_end, num_digits, @@ -4324,14 +4356,14 @@ rb_str2big_normal(VALUE arg, int base, int badcheck) s = str = StringValuePtr(arg); len = RSTRING_LEN(arg); if (len > 0 && *str == '-') { - len--; + len--; str++; positive_p = 0; } digits_start = str; if (!str2big_scan_digits(s, str, base, badcheck, &num_digits, &len)) - invalid_integer(arg); + invalid_integer(arg); digits_end = digits_start + len; maxpow_in_bdigit_dbl(base, &digits_per_bdigits_dbl); @@ -4366,14 +4398,14 @@ rb_str2big_karatsuba(VALUE arg, int base, int badcheck) s = str = StringValuePtr(arg); len = RSTRING_LEN(arg); if (len > 0 && *str == '-') { - len--; + len--; str++; positive_p = 0; } digits_start = str; if (!str2big_scan_digits(s, str, base, badcheck, &num_digits, &len)) - invalid_integer(arg); + invalid_integer(arg); digits_end = digits_start + len; maxpow_in_bdigit_dbl(base, &digits_per_bdigits_dbl); @@ -4387,7 +4419,7 @@ rb_str2big_karatsuba(VALUE arg, int base, int badcheck) return bignorm(z); } -#ifdef USE_GMP +#if USE_GMP VALUE rb_str2big_gmp(VALUE arg, int base, int badcheck) { @@ -4409,14 +4441,14 @@ rb_str2big_gmp(VALUE arg, int base, int badcheck) s = str = StringValuePtr(arg); len = RSTRING_LEN(arg); if (len > 0 && *str == '-') { - len--; + len--; str++; positive_p = 0; } digits_start = str; if (!str2big_scan_digits(s, str, base, badcheck, &num_digits, &len)) - invalid_integer(arg); + invalid_integer(arg); digits_end = digits_start + len; maxpow_in_bdigit_dbl(base, &digits_per_bdigits_dbl); @@ -4443,8 +4475,8 @@ rb_ull2big(unsigned LONG_LONG n) digits[0] = n; #else for (i = 0; i < bdigit_roomof(SIZEOF_LONG_LONG); i++) { - digits[i] = BIGLO(n); - n = BIGDN(n); + digits[i] = BIGLO(n); + n = BIGDN(n); } #endif @@ -4463,14 +4495,14 @@ rb_ll2big(LONG_LONG n) if (n < 0) { u = 1 + (unsigned LONG_LONG)(-(n + 1)); /* u = -n avoiding overflow */ - neg = 1; + neg = 1; } else { u = n; } big = rb_ull2big(u); if (neg) { - BIGNUM_SET_NEGATIVE_SIGN(big); + BIGNUM_SET_NEGATIVE_SIGN(big); } return big; } @@ -4478,14 +4510,14 @@ rb_ll2big(LONG_LONG n) VALUE rb_ull2inum(unsigned LONG_LONG n) { - if (POSFIXABLE(n)) return LONG2FIX(n); + if (POSFIXABLE(n)) return LONG2FIX((long)n); return rb_ull2big(n); } VALUE rb_ll2inum(LONG_LONG n) { - if (FIXABLE(n)) return LONG2FIX(n); + if (FIXABLE(n)) return LONG2FIX((long)n); return rb_ll2big(n); } @@ -4500,7 +4532,7 @@ rb_uint128t2big(uint128_t n) BDIGIT *digits = BDIGITS(big); for (i = 0; i < bdigit_roomof(SIZEOF_INT128_T); i++) { - digits[i] = BIGLO(RSHIFT(n ,BITSPERDIG*i)); + digits[i] = BIGLO(RSHIFT(n ,BITSPERDIG*i)); } i = bdigit_roomof(SIZEOF_INT128_T); @@ -4518,14 +4550,14 @@ rb_int128t2big(int128_t n) if (n < 0) { u = 1 + (uint128_t)(-(n + 1)); /* u = -n avoiding overflow */ - neg = 1; + neg = 1; } else { u = n; } big = rb_uint128t2big(u); if (neg) { - BIGNUM_SET_NEGATIVE_SIGN(big); + BIGNUM_SET_NEGATIVE_SIGN(big); } return big; } @@ -4554,11 +4586,14 @@ big_shift3(VALUE x, int lshift_p, size_t shift_numdigits, int shift_numbits) if (lshift_p) { if (LONG_MAX < shift_numdigits) { - rb_raise(rb_eArgError, "too big number"); + too_big: + rb_raise(rb_eRangeError, "shift width too big"); } s1 = shift_numdigits; s2 = shift_numbits; + if ((size_t)s1 != shift_numdigits) goto too_big; xn = BIGNUM_LEN(x); + if (LONG_MAX/SIZEOF_BDIGIT <= xn+s1) goto too_big; z = bignew(xn+s1+1, BIGNUM_SIGN(x)); zds = BDIGITS(z); BDIGITS_ZERO(zds, s1); @@ -4649,12 +4684,6 @@ static size_t base36_numdigits_cache[35][MAX_BASE36_POWER_TABLE_ENTRIES]; static void power_cache_init(void) { - int i, j; - for (i = 0; i < 35; ++i) { - for (j = 0; j < MAX_BASE36_POWER_TABLE_ENTRIES; ++j) { - base36_power_cache[i][j] = Qnil; - } - } } static inline VALUE @@ -4677,8 +4706,8 @@ power_cache_get_power(int base, int power_level, size_t *numdigits_ret) if (MAX_BASE36_POWER_TABLE_ENTRIES <= power_level) rb_bug("too big power number requested: maxpow_in_bdigit_dbl(%d)**(2**%d)", base, power_level); - if (NIL_P(base36_power_cache[base - 2][power_level])) { - VALUE power; + VALUE power = base36_power_cache[base - 2][power_level]; + if (!power) { size_t numdigits; if (power_level == 0) { int numdigits0; @@ -4694,11 +4723,11 @@ power_cache_get_power(int base, int power_level, size_t *numdigits_ret) rb_obj_hide(power); base36_power_cache[base - 2][power_level] = power; base36_numdigits_cache[base - 2][power_level] = numdigits; - rb_gc_register_mark_object(power); + rb_gc_register_mark_object(power); } if (numdigits_ret) *numdigits_ret = base36_numdigits_cache[base - 2][power_level]; - return base36_power_cache[base - 2][power_level]; + return power; } struct big2str_struct { @@ -4745,7 +4774,7 @@ big2str_2bdigits(struct big2str_struct *b2s, BDIGIT *xds, size_t xn, size_t tail } while (num); len = sizeof(buf) - j; big2str_alloc(b2s, len + taillen); - MEMCPY(b2s->ptr, buf + j, char, len); + MEMCPY(b2s->ptr, buf + j, char, len); } else { p = b2s->ptr; @@ -4762,7 +4791,7 @@ big2str_2bdigits(struct big2str_struct *b2s, BDIGIT *xds, size_t xn, size_t tail static void big2str_karatsuba(struct big2str_struct *b2s, BDIGIT *xds, size_t xn, size_t wn, - int power_level, size_t taillen) + int power_level, size_t taillen) { VALUE b; size_t half_numdigits, lower_numdigits; @@ -4792,17 +4821,17 @@ big2str_karatsuba(struct big2str_struct *b2s, BDIGIT *xds, size_t xn, size_t wn, */ if (xn == 0 || bary_zero_p(xds, xn)) { - if (b2s->ptr) { + if (b2s->ptr) { /* When x is zero, power_cache_get_power(base, power_level) should be cached already. */ power_cache_get_power(b2s->base, power_level, &len); - memset(b2s->ptr, '0', len); + memset(b2s->ptr, '0', len); b2s->ptr += len; - } + } return; } if (power_level == 0) { - big2str_2bdigits(b2s, xds, xn, taillen); + big2str_2bdigits(b2s, xds, xn, taillen); return; } @@ -4830,7 +4859,7 @@ big2str_karatsuba(struct big2str_struct *b2s, BDIGIT *xds, size_t xn, size_t wn, memset(b2s->ptr, '0', len); b2s->ptr += len; } - big2str_2bdigits(b2s, xds, xn, taillen); + big2str_2bdigits(b2s, xds, xn, taillen); } else { BDIGIT *qds, *rds; @@ -4934,11 +4963,11 @@ big2str_generic(VALUE x, int base) BARY_TRUNC(xds, xn); if (xn == 0) { - return rb_usascii_str_new2("0"); + return rb_usascii_str_new2("0"); } if (!valid_radix_p(base)) - invalid_radix(base); + invalid_radix(base); if (xn >= LONG_MAX/BITSPERDIG) { rb_raise(rb_eRangeError, "bignum too big to convert into `string'"); @@ -4975,7 +5004,7 @@ big2str_generic(VALUE x, int base) b2s_data.ptr = NULL; if (power_level == 0) { - big2str_2bdigits(&b2s_data, xds, xn, 0); + big2str_2bdigits(&b2s_data, xds, xn, 0); } else { VALUE tmpw = 0; @@ -4984,7 +5013,7 @@ big2str_generic(VALUE x, int base) wn = power_level * BIGDIVREM_EXTRA_WORDS + BIGNUM_LEN(power); wds = ALLOCV_N(BDIGIT, tmpw, xn + wn); MEMCPY(wds, xds, BDIGIT, xn); - big2str_karatsuba(&b2s_data, wds, xn, wn, power_level, 0); + big2str_karatsuba(&b2s_data, wds, xn, wn, power_level, 0); if (tmpw) ALLOCV_END(tmpw); } @@ -5003,11 +5032,10 @@ rb_big2str_generic(VALUE x, int base) return big2str_generic(x, base); } -#ifdef USE_GMP +#if USE_GMP static VALUE big2str_gmp(VALUE x, int base) { - const size_t nails = (sizeof(BDIGIT)-SIZEOF_BDIGIT)*CHAR_BIT; mpz_t mx; size_t size; VALUE str; @@ -5015,7 +5043,7 @@ big2str_gmp(VALUE x, int base) size_t xn = BIGNUM_LEN(x); mpz_init(mx); - mpz_import(mx, xn, -1, sizeof(BDIGIT), 0, nails, xds); + bdigits_to_mpz(mx, xds, xn); size = mpz_sizeinbase(mx, base); @@ -5051,7 +5079,7 @@ rb_big2str1(VALUE x, int base) size_t xn; if (FIXNUM_P(x)) { - return rb_fix2str(x, base); + return rb_fix2str(x, base); } bigtrunc(x); @@ -5060,11 +5088,11 @@ rb_big2str1(VALUE x, int base) BARY_TRUNC(xds, xn); if (xn == 0) { - return rb_usascii_str_new2("0"); + return rb_usascii_str_new2("0"); } if (!valid_radix_p(base)) - invalid_radix(base); + invalid_radix(base); if (xn >= LONG_MAX/BITSPERDIG) { rb_raise(rb_eRangeError, "bignum too big to convert into `string'"); @@ -5075,7 +5103,7 @@ rb_big2str1(VALUE x, int base) return big2str_base_poweroftwo(x, base); } -#ifdef USE_GMP +#if USE_GMP if (GMP_BIG2STR_DIGITS < xn) { return big2str_gmp(x, base); } @@ -5111,7 +5139,7 @@ big2ulong(VALUE x, const char *type) #else num = 0; for (i = 0; i < len; i++) { - num <<= BITSPERDIG; + num <<= BITSPERDIG; num += (unsigned long)ds[len - i - 1]; /* overflow is already checked */ } #endif @@ -5164,13 +5192,13 @@ big2ull(VALUE x, const char *type) if (len == 0) return 0; if (BIGSIZE(x) > SIZEOF_LONG_LONG) - rb_raise(rb_eRangeError, "bignum too big to convert into `%s'", type); + rb_raise(rb_eRangeError, "bignum too big to convert into `%s'", type); #if SIZEOF_LONG_LONG <= SIZEOF_BDIGIT num = (unsigned LONG_LONG)ds[0]; #else num = 0; for (i = 0; i < len; i++) { - num = BIGUP(num); + num = BIGUP(num); num += ds[len - i - 1]; } #endif @@ -5220,23 +5248,23 @@ dbl2big(double d) double u = (d < 0)?-d:d; if (isinf(d)) { - rb_raise(rb_eFloatDomainError, d < 0 ? "-Infinity" : "Infinity"); + rb_raise(rb_eFloatDomainError, d < 0 ? "-Infinity" : "Infinity"); } if (isnan(d)) { - rb_raise(rb_eFloatDomainError, "NaN"); + rb_raise(rb_eFloatDomainError, "NaN"); } while (1.0 <= u) { - u /= (double)(BIGRAD); - i++; + u /= (double)(BIGRAD); + i++; } z = bignew(i, d>=0); digits = BDIGITS(z); while (i--) { - u *= BIGRAD; - c = (BDIGIT)u; - u -= c; - digits[i] = c; + u *= BIGRAD; + c = (BDIGIT)u; + u -= c; + digits[i] = c; } return z; @@ -5256,28 +5284,28 @@ big2dbl(VALUE x) BDIGIT *ds = BDIGITS(x), dl; if (i) { - bits = i * BITSPERDIG - nlz(ds[i-1]); - if (bits > DBL_MANT_DIG+DBL_MAX_EXP) { - d = HUGE_VAL; - } - else { - if (bits > DBL_MANT_DIG+1) - lo = (bits -= DBL_MANT_DIG+1) / BITSPERDIG; - else - bits = 0; - while (--i > lo) { - d = ds[i] + BIGRAD*d; - } - dl = ds[i]; - if (bits && (dl & ((BDIGIT)1 << (bits %= BITSPERDIG)))) { - int carry = (dl & ~(BDIGMAX << bits)) != 0; - if (!carry) { - while (i-- > 0) { - carry = ds[i] != 0; - if (carry) break; - } - } - if (carry) { + bits = i * BITSPERDIG - nlz(ds[i-1]); + if (bits > DBL_MANT_DIG+DBL_MAX_EXP) { + d = HUGE_VAL; + } + else { + if (bits > DBL_MANT_DIG+1) + lo = (bits -= DBL_MANT_DIG+1) / BITSPERDIG; + else + bits = 0; + while (--i > lo) { + d = ds[i] + BIGRAD*d; + } + dl = ds[i]; + if (bits && (dl & ((BDIGIT)1 << (bits %= BITSPERDIG)))) { + int carry = (dl & ~(BDIGMAX << bits)) != 0; + if (!carry) { + while (i-- > 0) { + carry = ds[i] != 0; + if (carry) break; + } + } + if (carry) { BDIGIT mask = BDIGMAX; BDIGIT bit = 1; mask <<= bits; @@ -5285,19 +5313,19 @@ big2dbl(VALUE x) dl &= mask; dl += bit; dl = BIGLO(dl); - if (!dl) d += 1; - } - } - d = dl + BIGRAD*d; - if (lo) { - if (lo > INT_MAX / BITSPERDIG) - d = HUGE_VAL; - else if (lo < INT_MIN / BITSPERDIG) - d = 0.0; - else - d = ldexp(d, (int)(lo * BITSPERDIG)); - } - } + if (!dl) d += 1; + } + } + d = dl + BIGRAD*d; + if (lo) { + if (lo > INT_MAX / BITSPERDIG) + d = HUGE_VAL; + else if (lo < INT_MIN / BITSPERDIG) + d = 0.0; + else + d = ldexp(d, (int)(lo * BITSPERDIG)); + } + } } if (BIGNUM_NEGATIVE_P(x)) d = -d; return d; @@ -5309,11 +5337,11 @@ rb_big2dbl(VALUE x) double d = big2dbl(x); if (isinf(d)) { - rb_warning("Bignum out of Float range"); - if (d < 0.0) - d = -HUGE_VAL; - else - d = HUGE_VAL; + rb_warning("Integer out of Float range"); + if (d < 0.0) + d = -HUGE_VAL; + else + d = HUGE_VAL; } return d; } @@ -5368,13 +5396,22 @@ rb_integer_float_cmp(VALUE x, VALUE y) return INT2FIX(-1); } +#if SIZEOF_LONG * CHAR_BIT >= DBL_MANT_DIG /* assume FLT_RADIX == 2 */ +COMPILER_WARNING_PUSH +#if __has_warning("-Wimplicit-int-float-conversion") +COMPILER_WARNING_IGNORED(-Wimplicit-int-float-conversion) +#endif +static const double LONG_MAX_as_double = LONG_MAX; +COMPILER_WARNING_POP +#endif + VALUE rb_integer_float_eq(VALUE x, VALUE y) { double yd = RFLOAT_VALUE(y); double yi, yf; - if (isnan(yd) || isinf(yd)) + if (!isfinite(yd)) return Qfalse; yf = modf(yd, &yi); if (yf != 0) @@ -5382,48 +5419,45 @@ rb_integer_float_eq(VALUE x, VALUE y) if (FIXNUM_P(x)) { #if SIZEOF_LONG * CHAR_BIT < DBL_MANT_DIG /* assume FLT_RADIX == 2 */ double xd = (double)FIX2LONG(x); - if (xd != yd) - return Qfalse; - return Qtrue; + return RBOOL(xd == yd); #else long xn, yn; - if (yi < LONG_MIN || LONG_MAX < yi) + if (yi < LONG_MIN || LONG_MAX_as_double <= yi) return Qfalse; xn = FIX2LONG(x); yn = (long)yi; - if (xn != yn) - return Qfalse; - return Qtrue; + return RBOOL(xn == yn); #endif } y = rb_dbl2big(yi); return rb_big_eq(x, y); } + VALUE rb_big_cmp(VALUE x, VALUE y) { if (FIXNUM_P(y)) { - x = bigfixize(x); + x = bigfixize(x); if (FIXNUM_P(x)) { - /* SIGNED_VALUE and Fixnum have same sign-bits, same - * order */ - SIGNED_VALUE sx = (SIGNED_VALUE)x, sy = (SIGNED_VALUE)y; - if (sx < sy) return INT2FIX(-1); - return INT2FIX(sx > sy); + /* SIGNED_VALUE and Fixnum have same sign-bits, same + * order */ + SIGNED_VALUE sx = (SIGNED_VALUE)x, sy = (SIGNED_VALUE)y; + if (sx < sy) return INT2FIX(-1); + return INT2FIX(sx > sy); } } else if (RB_BIGNUM_TYPE_P(y)) { - if (BIGNUM_SIGN(x) == BIGNUM_SIGN(y)) { - int cmp = bary_cmp(BDIGITS(x), BIGNUM_LEN(x), BDIGITS(y), BIGNUM_LEN(y)); - return INT2FIX(BIGNUM_SIGN(x) ? cmp : -cmp); - } + if (BIGNUM_SIGN(x) == BIGNUM_SIGN(y)) { + int cmp = bary_cmp(BDIGITS(x), BIGNUM_LEN(x), BDIGITS(y), BIGNUM_LEN(y)); + return INT2FIX(BIGNUM_SIGN(x) ? cmp : -cmp); + } } else if (RB_FLOAT_TYPE_P(y)) { return rb_integer_float_cmp(x, y); } else { - return rb_num_coerce_cmp(x, y, idCmp); + return rb_num_coerce_cmp(x, y, idCmp); } return INT2FIX(BIGNUM_SIGN(x) ? 1 : -1); } @@ -5442,30 +5476,30 @@ big_op(VALUE x, VALUE y, enum big_op_t op) int n; if (RB_INTEGER_TYPE_P(y)) { - rel = rb_big_cmp(x, y); + rel = rb_big_cmp(x, y); } else if (RB_FLOAT_TYPE_P(y)) { rel = rb_integer_float_cmp(x, y); } else { - ID id = 0; - switch (op) { - case big_op_gt: id = '>'; break; - case big_op_ge: id = idGE; break; - case big_op_lt: id = '<'; break; - case big_op_le: id = idLE; break; - } - return rb_num_coerce_relop(x, y, id); + ID id = 0; + switch (op) { + case big_op_gt: id = '>'; break; + case big_op_ge: id = idGE; break; + case big_op_lt: id = '<'; break; + case big_op_le: id = idLE; break; + } + return rb_num_coerce_relop(x, y, id); } if (NIL_P(rel)) return Qfalse; n = FIX2INT(rel); switch (op) { - case big_op_gt: return n > 0 ? Qtrue : Qfalse; - case big_op_ge: return n >= 0 ? Qtrue : Qfalse; - case big_op_lt: return n < 0 ? Qtrue : Qfalse; - case big_op_le: return n <= 0 ? Qtrue : Qfalse; + case big_op_gt: return RBOOL(n > 0); + case big_op_ge: return RBOOL(n >= 0); + case big_op_lt: return RBOOL(n < 0); + case big_op_le: return RBOOL(n <= 0); } return Qundef; } @@ -5499,8 +5533,8 @@ rb_big_le(VALUE x, VALUE y) * big == obj -> true or false * * Returns <code>true</code> only if <i>obj</i> has the same value - * as <i>big</i>. Contrast this with <code>Integer#eql?</code>, which - * requires <i>obj</i> to be a <code>Integer</code>. + * as <i>big</i>. Contrast this with Integer#eql?, which requires + * <i>obj</i> to be an Integer. * * 68719476736 == 68719476736.0 #=> true */ @@ -5509,7 +5543,7 @@ VALUE rb_big_eq(VALUE x, VALUE y) { if (FIXNUM_P(y)) { - return bignorm(x) == y ? Qtrue : Qfalse; + return RBOOL(bignorm(x) == y); } else if (RB_BIGNUM_TYPE_P(y)) { } @@ -5517,12 +5551,11 @@ rb_big_eq(VALUE x, VALUE y) return rb_integer_float_eq(x, y); } else { - return rb_equal(y, x); + return rb_equal(y, x); } if (BIGNUM_SIGN(x) != BIGNUM_SIGN(y)) return Qfalse; if (BIGNUM_LEN(x) != BIGNUM_LEN(y)) return Qfalse; - if (MEMCMP(BDIGITS(x),BDIGITS(y),BDIGIT,BIGNUM_LEN(y)) != 0) return Qfalse; - return Qtrue; + return RBOOL(MEMCMP(BDIGITS(x),BDIGITS(y),BDIGIT,BIGNUM_LEN(y)) == 0); } VALUE @@ -5531,8 +5564,7 @@ rb_big_eql(VALUE x, VALUE y) if (!RB_BIGNUM_TYPE_P(y)) return Qfalse; if (BIGNUM_SIGN(x) != BIGNUM_SIGN(y)) return Qfalse; if (BIGNUM_LEN(x) != BIGNUM_LEN(y)) return Qfalse; - if (MEMCMP(BDIGITS(x),BDIGITS(y),BDIGIT,BIGNUM_LEN(y)) != 0) return Qfalse; - return Qtrue; + return RBOOL(MEMCMP(BDIGITS(x),BDIGITS(y),BDIGIT,BIGNUM_LEN(y)) == 0); } VALUE @@ -5626,10 +5658,10 @@ bigsub_int(VALUE x, long y0) assert(xn == zn); num = (BDIGIT_DBL_SIGNED)xds[0] - y; if (xn == 1 && num < 0) { - BIGNUM_NEGATE(z); - zds[0] = (BDIGIT)-num; - RB_GC_GUARD(x); - return bignorm(z); + BIGNUM_NEGATE(z); + zds[0] = (BDIGIT)-num; + RB_GC_GUARD(x); + return bignorm(z); } zds[0] = BIGLO(num); num = BIGDN(num); @@ -5641,10 +5673,10 @@ bigsub_int(VALUE x, long y0) num = 0; for (i=0; i < xn; i++) { if (y == 0) goto y_is_zero_x; - num += (BDIGIT_DBL_SIGNED)xds[i] - BIGLO(y); - zds[i] = BIGLO(num); - num = BIGDN(num); - y = BIGDN(y); + num += (BDIGIT_DBL_SIGNED)xds[i] - BIGLO(y); + zds[i] = BIGLO(num); + num = BIGDN(num); + y = BIGDN(y); } for (; i < zn; i++) { if (y == 0) goto y_is_zero_z; @@ -5659,9 +5691,9 @@ bigsub_int(VALUE x, long y0) for (; i < xn; i++) { y_is_zero_x: if (num == 0) goto num_is_zero_x; - num += xds[i]; - zds[i] = BIGLO(num); - num = BIGDN(num); + num += xds[i]; + zds[i] = BIGLO(num); + num = BIGDN(num); } #if SIZEOF_BDIGIT < SIZEOF_LONG for (; i < zn; i++) { @@ -5675,7 +5707,7 @@ bigsub_int(VALUE x, long y0) for (; i < xn; i++) { num_is_zero_x: - zds[i] = xds[i]; + zds[i] = xds[i]; } #if SIZEOF_BDIGIT < SIZEOF_LONG for (; i < zn; i++) { @@ -5689,7 +5721,7 @@ bigsub_int(VALUE x, long y0) assert(num == 0 || num == -1); if (num < 0) { get2comp(z); - BIGNUM_NEGATE(z); + BIGNUM_NEGATE(z); } RB_GC_GUARD(x); return bignorm(z); @@ -5732,17 +5764,17 @@ bigadd_int(VALUE x, long y) num = 0; for (i=0; i < xn; i++) { if (y == 0) goto y_is_zero_x; - num += (BDIGIT_DBL)xds[i] + BIGLO(y); - zds[i] = BIGLO(num); - num = BIGDN(num); - y = BIGDN(y); + num += (BDIGIT_DBL)xds[i] + BIGLO(y); + zds[i] = BIGLO(num); + num = BIGDN(num); + y = BIGDN(y); } for (; i < zn; i++) { if (y == 0) goto y_is_zero_z; - num += BIGLO(y); - zds[i] = BIGLO(num); - num = BIGDN(num); - y = BIGDN(y); + num += BIGLO(y); + zds[i] = BIGLO(num); + num = BIGDN(num); + y = BIGDN(y); } goto finish; @@ -5751,25 +5783,25 @@ bigadd_int(VALUE x, long y) for (;i < xn; i++) { y_is_zero_x: if (num == 0) goto num_is_zero_x; - num += (BDIGIT_DBL)xds[i]; - zds[i] = BIGLO(num); - num = BIGDN(num); + num += (BDIGIT_DBL)xds[i]; + zds[i] = BIGLO(num); + num = BIGDN(num); } for (; i < zn; i++) { y_is_zero_z: if (num == 0) goto num_is_zero_z; - zds[i] = BIGLO(num); - num = BIGDN(num); + zds[i] = BIGLO(num); + num = BIGDN(num); } goto finish; for (;i < xn; i++) { num_is_zero_x: - zds[i] = xds[i]; + zds[i] = xds[i]; } for (; i < zn; i++) { num_is_zero_z: - zds[i] = 0; + zds[i] = 0; } goto finish; @@ -5786,15 +5818,15 @@ bigadd(VALUE x, VALUE y, int sign) sign = (sign == BIGNUM_SIGN(y)); if (BIGNUM_SIGN(x) != sign) { - if (sign) return bigsub(y, x); - return bigsub(x, y); + if (sign) return bigsub(y, x); + return bigsub(x, y); } if (BIGNUM_LEN(x) > BIGNUM_LEN(y)) { - len = BIGNUM_LEN(x) + 1; + len = BIGNUM_LEN(x) + 1; } else { - len = BIGNUM_LEN(y) + 1; + len = BIGNUM_LEN(y) + 1; } z = bignew(len, sign); @@ -5811,26 +5843,26 @@ rb_big_plus(VALUE x, VALUE y) long n; if (FIXNUM_P(y)) { - n = FIX2LONG(y); - if ((n > 0) != BIGNUM_SIGN(x)) { - if (n < 0) { - n = -n; - } - return bigsub_int(x, n); - } - if (n < 0) { - n = -n; - } - return bigadd_int(x, n); + n = FIX2LONG(y); + if ((n > 0) != BIGNUM_SIGN(x)) { + if (n < 0) { + n = -n; + } + return bigsub_int(x, n); + } + if (n < 0) { + n = -n; + } + return bigadd_int(x, n); } else if (RB_BIGNUM_TYPE_P(y)) { - return bignorm(bigadd(x, y, 1)); + return bignorm(bigadd(x, y, 1)); } else if (RB_FLOAT_TYPE_P(y)) { - return DBL2NUM(rb_big2dbl(x) + RFLOAT_VALUE(y)); + return DBL2NUM(rb_big2dbl(x) + RFLOAT_VALUE(y)); } else { - return rb_num_coerce_bin(x, y, '+'); + return rb_num_coerce_bin(x, y, '+'); } } @@ -5840,26 +5872,26 @@ rb_big_minus(VALUE x, VALUE y) long n; if (FIXNUM_P(y)) { - n = FIX2LONG(y); - if ((n > 0) != BIGNUM_SIGN(x)) { - if (n < 0) { - n = -n; - } - return bigadd_int(x, n); - } - if (n < 0) { - n = -n; - } - return bigsub_int(x, n); + n = FIX2LONG(y); + if ((n > 0) != BIGNUM_SIGN(x)) { + if (n < 0) { + n = -n; + } + return bigadd_int(x, n); + } + if (n < 0) { + n = -n; + } + return bigsub_int(x, n); } else if (RB_BIGNUM_TYPE_P(y)) { - return bignorm(bigadd(x, y, 0)); + return bignorm(bigadd(x, y, 0)); } else if (RB_FLOAT_TYPE_P(y)) { - return DBL2NUM(rb_big2dbl(x) - RFLOAT_VALUE(y)); + return DBL2NUM(rb_big2dbl(x) - RFLOAT_VALUE(y)); } else { - return rb_num_coerce_bin(x, y, '-'); + return rb_num_coerce_bin(x, y, '-'); } } @@ -5918,15 +5950,15 @@ VALUE rb_big_mul(VALUE x, VALUE y) { if (FIXNUM_P(y)) { - y = rb_int2big(FIX2LONG(y)); + y = rb_int2big(FIX2LONG(y)); } else if (RB_BIGNUM_TYPE_P(y)) { } else if (RB_FLOAT_TYPE_P(y)) { - return DBL2NUM(rb_big2dbl(x) * RFLOAT_VALUE(y)); + return DBL2NUM(rb_big2dbl(x) * RFLOAT_VALUE(y)); } else { - return rb_num_coerce_bin(x, y, '*'); + return rb_num_coerce_bin(x, y, '*'); } return bignorm(bigmul0(x, y)); @@ -5953,21 +5985,21 @@ bigdivrem(VALUE x, VALUE y, volatile VALUE *divp, volatile VALUE *modp) BARY_TRUNC(xds, xn); if (xn < yn || (xn == yn && xds[xn - 1] < yds[yn - 1])) { - if (divp) *divp = rb_int2big(0); - if (modp) *modp = x; - return Qnil; + if (divp) *divp = rb_int2big(0); + if (modp) *modp = x; + return Qnil; } if (yn == 1) { - dd = yds[0]; - z = bignew(xn, BIGNUM_SIGN(x)==BIGNUM_SIGN(y)); - zds = BDIGITS(z); + dd = yds[0]; + z = bignew(xn, BIGNUM_SIGN(x)==BIGNUM_SIGN(y)); + zds = BDIGITS(z); dd = bigdivrem_single(zds, xds, xn, dd); - if (modp) { - *modp = rb_uint2big((uintptr_t)dd); - BIGNUM_SET_SIGN(*modp, BIGNUM_SIGN(x)); - } - if (divp) *divp = z; - return Qnil; + if (modp) { + *modp = rb_uint2big((uintptr_t)dd); + BIGNUM_SET_SIGN(*modp, BIGNUM_SIGN(x)); + } + if (divp) *divp = z; + return Qnil; } if (xn == 2 && yn == 2) { BDIGIT_DBL x0 = bary2bdigitdbl(xds, 2); @@ -6032,11 +6064,11 @@ bigdivmod(VALUE x, VALUE y, volatile VALUE *divp, volatile VALUE *modp) bigdivrem(x, y, divp, &mod); if (BIGNUM_SIGN(x) != BIGNUM_SIGN(y) && !BIGZEROP(mod)) { - if (divp) *divp = bigadd(*divp, rb_int2big(1), 0); - if (modp) *modp = bigadd(mod, y, 1); + if (divp) *divp = bigadd(*divp, rb_int2big(1), 0); + if (modp) *modp = bigadd(mod, y, 1); } else if (modp) { - *modp = mod; + *modp = mod; } } @@ -6047,25 +6079,25 @@ rb_big_divide(VALUE x, VALUE y, ID op) VALUE z; if (FIXNUM_P(y)) { - y = rb_int2big(FIX2LONG(y)); + y = rb_int2big(FIX2LONG(y)); } else if (RB_BIGNUM_TYPE_P(y)) { } else if (RB_FLOAT_TYPE_P(y)) { - if (op == '/') { + if (op == '/') { double dx = rb_big2dbl(x); return rb_flo_div_flo(DBL2NUM(dx), y); - } - else { + } + else { VALUE v; - double dy = RFLOAT_VALUE(y); - if (dy == 0.0) rb_num_zerodiv(); + double dy = RFLOAT_VALUE(y); + if (dy == 0.0) rb_num_zerodiv(); v = rb_big_divide(x, y, '/'); return rb_dbl2big(RFLOAT_VALUE(v)); - } + } } else { - return rb_num_coerce_bin(x, y, op); + return rb_num_coerce_bin(x, y, op); } bigdivmod(x, y, &z, 0); @@ -6081,7 +6113,7 @@ rb_big_div(VALUE x, VALUE y) VALUE rb_big_idiv(VALUE x, VALUE y) { - return rb_big_divide(x, y, rb_intern("div")); + return rb_big_divide(x, y, idDiv); } VALUE @@ -6090,10 +6122,10 @@ rb_big_modulo(VALUE x, VALUE y) VALUE z; if (FIXNUM_P(y)) { - y = rb_int2big(FIX2LONG(y)); + y = rb_int2big(FIX2LONG(y)); } else if (!RB_BIGNUM_TYPE_P(y)) { - return rb_num_coerce_bin(x, y, '%'); + return rb_num_coerce_bin(x, y, '%'); } bigdivmod(x, y, 0, &z); @@ -6106,10 +6138,10 @@ rb_big_remainder(VALUE x, VALUE y) VALUE z; if (FIXNUM_P(y)) { - y = rb_int2big(FIX2LONG(y)); + y = rb_int2big(FIX2LONG(y)); } else if (!RB_BIGNUM_TYPE_P(y)) { - return rb_num_coerce_bin(x, y, rb_intern("remainder")); + return rb_num_coerce_bin(x, y, rb_intern("remainder")); } bigdivrem(x, y, 0, &z); @@ -6122,10 +6154,10 @@ rb_big_divmod(VALUE x, VALUE y) VALUE div, mod; if (FIXNUM_P(y)) { - y = rb_int2big(FIX2LONG(y)); + y = rb_int2big(FIX2LONG(y)); } else if (!RB_BIGNUM_TYPE_P(y)) { - return rb_num_coerce_bin(x, y, rb_intern("divmod")); + return rb_num_coerce_bin(x, y, idDivmod); } bigdivmod(x, y, &div, &mod); @@ -6136,9 +6168,9 @@ static VALUE big_shift(VALUE x, long n) { if (n < 0) - return big_lshift(x, 1+(unsigned long)(-(n+1))); + return big_lshift(x, 1+(unsigned long)(-(n+1))); else if (n > 0) - return big_rshift(x, (unsigned long)n); + return big_rshift(x, (unsigned long)n); return x; } @@ -6162,9 +6194,9 @@ big_fdiv(VALUE x, VALUE y, long ey) l = ex - ey; #if SIZEOF_LONG > SIZEOF_INT { - /* Visual C++ can't be here */ - if (l > INT_MAX) return HUGE_VAL; - if (l < INT_MIN) return 0.0; + /* Visual C++ can't be here */ + if (l > INT_MAX) return HUGE_VAL; + if (l < INT_MIN) return 0.0; } #endif return ldexp(big2dbl(z), (int)l); @@ -6198,22 +6230,22 @@ rb_big_fdiv_double(VALUE x, VALUE y) dx = big2dbl(x); if (FIXNUM_P(y)) { - dy = (double)FIX2LONG(y); - if (isinf(dx)) - return big_fdiv_int(x, rb_int2big(FIX2LONG(y))); + dy = (double)FIX2LONG(y); + if (isinf(dx)) + return big_fdiv_int(x, rb_int2big(FIX2LONG(y))); } else if (RB_BIGNUM_TYPE_P(y)) { - return big_fdiv_int(x, y); + return big_fdiv_int(x, y); } else if (RB_FLOAT_TYPE_P(y)) { - dy = RFLOAT_VALUE(y); - if (isnan(dy)) - return dy; - if (isinf(dx)) - return big_fdiv_float(x, y); + dy = RFLOAT_VALUE(y); + if (isnan(dy)) + return dy; + if (isinf(dx)) + return big_fdiv_float(x, y); } else { - return NUM2DBL(rb_num_coerce_bin(x, y, rb_intern("fdiv"))); + return NUM2DBL(rb_num_coerce_bin(x, y, idFdiv)); } v = rb_flo_div_flo(DBL2NUM(dx), DBL2NUM(dy)); return NUM2DBL(v); @@ -6235,53 +6267,53 @@ rb_big_pow(VALUE x, VALUE y) if (y == INT2FIX(0)) return INT2FIX(1); if (y == INT2FIX(1)) return x; if (RB_FLOAT_TYPE_P(y)) { - d = RFLOAT_VALUE(y); - if ((BIGNUM_NEGATIVE_P(x) && !BIGZEROP(x))) { + d = RFLOAT_VALUE(y); + if ((BIGNUM_NEGATIVE_P(x) && !BIGZEROP(x))) { return rb_dbl_complex_new_polar_pi(pow(-rb_big2dbl(x), d), d); - } + } } else if (RB_BIGNUM_TYPE_P(y)) { - y = bignorm(y); - if (FIXNUM_P(y)) - goto again; - rb_warn("in a**b, b may be too big"); - d = rb_big2dbl(y); + y = bignorm(y); + if (FIXNUM_P(y)) + goto again; + rb_warn("in a**b, b may be too big"); + d = rb_big2dbl(y); } else if (FIXNUM_P(y)) { - yy = FIX2LONG(y); + yy = FIX2LONG(y); if (yy < 0) { - x = rb_big_pow(x, INT2NUM(-yy)); + x = rb_big_pow(x, LONG2NUM(-yy)); if (RB_INTEGER_TYPE_P(x)) return rb_rational_raw(INT2FIX(1), x); else return DBL2NUM(1.0 / NUM2DBL(x)); } - else { - VALUE z = 0; - SIGNED_VALUE mask; + else { + VALUE z = 0; + SIGNED_VALUE mask; const size_t xbits = rb_absint_numwords(x, 1, NULL); - const size_t BIGLEN_LIMIT = 32*1024*1024; + const size_t BIGLEN_LIMIT = 32*1024*1024; - if (xbits == (size_t)-1 || + if (xbits == (size_t)-1 || (xbits > BIGLEN_LIMIT) || (xbits * yy > BIGLEN_LIMIT)) { - rb_warn("in a**b, b may be too big"); - d = (double)yy; - } - else { - for (mask = FIXNUM_MAX + 1; mask; mask >>= 1) { - if (z) z = bigsq(z); - if (yy & mask) { - z = z ? bigtrunc(bigmul0(z, x)) : x; - } - } - return bignorm(z); - } - } + rb_warn("in a**b, b may be too big"); + d = (double)yy; + } + else { + for (mask = FIXNUM_MAX + 1; mask; mask >>= 1) { + if (z) z = bigsq(z); + if (yy & mask) { + z = z ? bigtrunc(bigmul0(z, x)) : x; + } + } + return bignorm(z); + } + } } else { - return rb_num_coerce_bin(x, y, idPow); + return rb_num_coerce_bin(x, y, idPow); } return DBL2NUM(pow(rb_big2dbl(x), d)); } @@ -6301,8 +6333,8 @@ bigand_int(VALUE x, long xn, BDIGIT hibitsx, long y) xds = BDIGITS(x); #if SIZEOF_BDIGIT >= SIZEOF_LONG if (!hibitsy) { - y &= xds[0]; - return LONG2NUM(y); + y &= xds[0]; + return LONG2NUM(y); } #endif @@ -6331,10 +6363,10 @@ bigand_int(VALUE x, long xn, BDIGIT hibitsx, long y) } #endif for (;i < xn; i++) { - zds[i] = xds[i] & hibitsy; + zds[i] = xds[i] & hibitsy; } for (;i < zn; i++) { - zds[i] = hibitsx & hibitsy; + zds[i] = hibitsx & hibitsy; } twocomp2abs_bang(z, hibitsx && hibitsy); RB_GC_GUARD(x); @@ -6354,12 +6386,12 @@ rb_big_and(VALUE x, VALUE y) long tmpn; if (!RB_INTEGER_TYPE_P(y)) { - return rb_num_coerce_bit(x, y, '&'); + return rb_num_coerce_bit(x, y, '&'); } hibitsx = abs2twocomp(&x, &xn); if (FIXNUM_P(y)) { - return bigand_int(x, xn, hibitsx, FIX2LONG(y)); + return bigand_int(x, xn, hibitsx, FIX2LONG(y)); } hibitsy = abs2twocomp(&y, &yn); if (xn > yn) { @@ -6381,10 +6413,10 @@ rb_big_and(VALUE x, VALUE y) zds = BDIGITS(z); for (i=0; i<n1; i++) { - zds[i] = ds1[i] & ds2[i]; + zds[i] = ds1[i] & ds2[i]; } for (; i<n2; i++) { - zds[i] = hibits1 & ds2[i]; + zds[i] = hibits1 & ds2[i]; } twocomp2abs_bang(z, hibits1 && hibits2); RB_GC_GUARD(x); @@ -6473,12 +6505,12 @@ rb_big_or(VALUE x, VALUE y) long tmpn; if (!RB_INTEGER_TYPE_P(y)) { - return rb_num_coerce_bit(x, y, '|'); + return rb_num_coerce_bit(x, y, '|'); } hibitsx = abs2twocomp(&x, &xn); if (FIXNUM_P(y)) { - return bigor_int(x, xn, hibitsx, FIX2LONG(y)); + return bigor_int(x, xn, hibitsx, FIX2LONG(y)); } hibitsy = abs2twocomp(&y, &yn); if (xn > yn) { @@ -6500,10 +6532,10 @@ rb_big_or(VALUE x, VALUE y) zds = BDIGITS(z); for (i=0; i<n1; i++) { - zds[i] = ds1[i] | ds2[i]; + zds[i] = ds1[i] | ds2[i]; } for (; i<n2; i++) { - zds[i] = hibits1 | ds2[i]; + zds[i] = hibits1 | ds2[i]; } twocomp2abs_bang(z, hibits1 || hibits2); RB_GC_GUARD(x); @@ -6567,12 +6599,12 @@ rb_big_xor(VALUE x, VALUE y) long tmpn; if (!RB_INTEGER_TYPE_P(y)) { - return rb_num_coerce_bit(x, y, '^'); + return rb_num_coerce_bit(x, y, '^'); } hibitsx = abs2twocomp(&x, &xn); if (FIXNUM_P(y)) { - return bigxor_int(x, xn, hibitsx, FIX2LONG(y)); + return bigxor_int(x, xn, hibitsx, FIX2LONG(y)); } hibitsy = abs2twocomp(&y, &yn); if (xn > yn) { @@ -6591,10 +6623,10 @@ rb_big_xor(VALUE x, VALUE y) zds = BDIGITS(z); for (i=0; i<n1; i++) { - zds[i] = ds1[i] ^ ds2[i]; + zds[i] = ds1[i] ^ ds2[i]; } for (; i<n2; i++) { - zds[i] = hibitsx ^ ds2[i]; + zds[i] = hibitsx ^ ds2[i]; } twocomp2abs_bang(z, (hibits1 ^ hibits2) != 0); RB_GC_GUARD(x); @@ -6610,25 +6642,25 @@ rb_big_lshift(VALUE x, VALUE y) int shift_numbits; for (;;) { - if (FIXNUM_P(y)) { - long l = FIX2LONG(y); + if (FIXNUM_P(y)) { + long l = FIX2LONG(y); unsigned long shift; - if (0 <= l) { - lshift_p = 1; + if (0 <= l) { + lshift_p = 1; shift = l; } else { - lshift_p = 0; - shift = 1+(unsigned long)(-(l+1)); - } + lshift_p = 0; + shift = 1+(unsigned long)(-(l+1)); + } shift_numbits = (int)(shift & (BITSPERDIG-1)); shift_numdigits = shift >> bit_length(BITSPERDIG-1); return bignorm(big_shift3(x, lshift_p, shift_numdigits, shift_numbits)); - } - else if (RB_BIGNUM_TYPE_P(y)) { + } + else if (RB_BIGNUM_TYPE_P(y)) { return bignorm(big_shift2(x, 1, y)); - } - y = rb_to_int(y); + } + y = rb_to_int(y); } } @@ -6640,8 +6672,8 @@ rb_big_rshift(VALUE x, VALUE y) int shift_numbits; for (;;) { - if (FIXNUM_P(y)) { - long l = FIX2LONG(y); + if (FIXNUM_P(y)) { + long l = FIX2LONG(y); unsigned long shift; if (0 <= l) { lshift_p = 0; @@ -6649,16 +6681,16 @@ rb_big_rshift(VALUE x, VALUE y) } else { lshift_p = 1; - shift = 1+(unsigned long)(-(l+1)); - } + shift = 1+(unsigned long)(-(l+1)); + } shift_numbits = (int)(shift & (BITSPERDIG-1)); shift_numdigits = shift >> bit_length(BITSPERDIG-1); return bignorm(big_shift3(x, lshift_p, shift_numdigits, shift_numbits)); - } - else if (RB_BIGNUM_TYPE_P(y)) { + } + else if (RB_BIGNUM_TYPE_P(y)) { return bignorm(big_shift2(x, 0, y)); - } - y = rb_to_int(y); + } + y = rb_to_int(y); } } @@ -6672,29 +6704,29 @@ rb_big_aref(VALUE x, VALUE y) BDIGIT bit; if (RB_BIGNUM_TYPE_P(y)) { - if (BIGNUM_NEGATIVE_P(y)) - return INT2FIX(0); - bigtrunc(y); - if (BIGSIZE(y) > sizeof(size_t)) { - out_of_range: - return BIGNUM_SIGN(x) ? INT2FIX(0) : INT2FIX(1); - } + if (BIGNUM_NEGATIVE_P(y)) + return INT2FIX(0); + bigtrunc(y); + if (BIGSIZE(y) > sizeof(size_t)) { + return BIGNUM_SIGN(x) ? INT2FIX(0) : INT2FIX(1); + } #if SIZEOF_SIZE_T <= SIZEOF_LONG - shift = big2ulong(y, "long"); + shift = big2ulong(y, "long"); #else - shift = big2ull(y, "long long"); + shift = big2ull(y, "long long"); #endif } else { - l = NUM2LONG(y); - if (l < 0) return INT2FIX(0); - shift = (size_t)l; + l = NUM2LONG(y); + if (l < 0) return INT2FIX(0); + shift = (size_t)l; } s1 = shift/BITSPERDIG; s2 = shift%BITSPERDIG; bit = (BDIGIT)1 << s2; - if (s1 >= BIGNUM_LEN(x)) goto out_of_range; + if (s1 >= BIGNUM_LEN(x)) + return BIGNUM_SIGN(x) ? INT2FIX(0) : INT2FIX(1); xds = BDIGITS(x); if (BIGNUM_POSITIVE_P(x)) @@ -6718,14 +6750,15 @@ rb_big_hash(VALUE x) /* * call-seq: - * big.coerce(numeric) -> array + * int.coerce(numeric) -> array * - * Returns an array with both a +numeric+ and a +big+ represented as Bignum - * objects. + * Returns an array with both a +numeric+ and a +int+ represented as + * Integer objects or Float objects. * - * This is achieved by converting +numeric+ to a Bignum. + * This is achieved by converting +numeric+ to an Integer or a Float. * - * A TypeError is raised if the +numeric+ is not a Fixnum or Bignum type. + * A TypeError is raised if the +numeric+ is not an Integer or a Float + * type. * * (0x3FFFFFFFFFFFFFFF+1).coerce(42) #=> [42, 4611686018427387904] */ @@ -6747,8 +6780,8 @@ VALUE rb_big_abs(VALUE x) { if (BIGNUM_NEGATIVE_P(x)) { - x = rb_big_clone(x); - BIGNUM_SET_POSITIVE_SIGN(x); + x = rb_big_clone(x); + BIGNUM_SET_POSITIVE_SIGN(x); } return x; } @@ -6815,17 +6848,14 @@ rb_big_bit_length(VALUE big) VALUE rb_big_odd_p(VALUE num) { - if (BIGNUM_LEN(num) != 0 && BDIGITS(num)[0] & 1) { - return Qtrue; - } - return Qfalse; + return RBOOL(BIGNUM_LEN(num) != 0 && BDIGITS(num)[0] & 1); } VALUE rb_big_even_p(VALUE num) { if (BIGNUM_LEN(num) != 0 && BDIGITS(num)[0] & 1) { - return Qfalse; + return Qfalse; } return Qtrue; } @@ -6856,26 +6886,34 @@ estimate_initial_sqrt(VALUE *xp, const size_t xn, const BDIGIT *nds, size_t len) double f; if (rshift > 0) { - lowbits = (BDIGIT)d & ~(~(BDIGIT)1U << rshift); - d >>= rshift; + lowbits = (BDIGIT)d & ~(~(BDIGIT)1U << rshift); + d >>= rshift; } else if (rshift < 0) { - d <<= -rshift; - d |= nds[len-dbl_per_bdig-1] >> (BITSPERDIG+rshift); + d <<= -rshift; + d |= nds[len-dbl_per_bdig-1] >> (BITSPERDIG+rshift); } f = sqrt(BDIGIT_DBL_TO_DOUBLE(d)); d = (BDIGIT_DBL)ceil(f); if (BDIGIT_DBL_TO_DOUBLE(d) == f) { - if (lowbits || (lowbits = !bary_zero_p(nds, len-dbl_per_bdig))) - ++d; + if (lowbits || (lowbits = !bary_zero_p(nds, len-dbl_per_bdig))) + ++d; } else { - lowbits = 1; + lowbits = 1; } rshift /= 2; rshift += (2-(len&1))*BITSPERDIG/2; if (rshift >= 0) { - d <<= rshift; + if (nlz((BDIGIT)d) + rshift >= BITSPERDIG) { + /* (d << rshift) does cause overflow. + * example: Integer.sqrt(0xffff_ffff_ffff_ffff ** 2) + */ + d = ~(BDIGIT_DBL)0; + } + else { + d <<= rshift; + } } BDIGITS_ZERO(xds, xn-2); bdigitdbl2bary(&xds[xn-2], 2, d); @@ -6894,52 +6932,49 @@ rb_big_isqrt(VALUE n) BDIGIT *xds; if (len <= 2) { - BDIGIT sq = rb_bdigit_dbl_isqrt(bary2bdigitdbl(nds, len)); + BDIGIT sq = rb_bdigit_dbl_isqrt(bary2bdigitdbl(nds, len)); #if SIZEOF_BDIGIT > SIZEOF_LONG - return ULL2NUM(sq); + return ULL2NUM(sq); #else - return ULONG2NUM(sq); + return ULONG2NUM(sq); #endif } else if ((xds = estimate_initial_sqrt(&x, xn, nds, len)) != 0) { - size_t tn = xn + BIGDIVREM_EXTRA_WORDS; - VALUE t = bignew_1(0, tn, 1); - BDIGIT *tds = BDIGITS(t); - tn = BIGNUM_LEN(t); - - /* t = n/x */ - while (bary_divmod_branch(tds, tn, NULL, 0, nds, len, xds, xn), - bary_cmp(tds, tn, xds, xn) < 0) { - int carry; - BARY_TRUNC(tds, tn); - /* x = (x+t)/2 */ - carry = bary_add(xds, xn, xds, xn, tds, tn); - bary_small_rshift(xds, xds, xn, 1, carry); - tn = BIGNUM_LEN(t); - } - rb_big_realloc(t, 0); - rb_gc_force_recycle(t); + size_t tn = xn + BIGDIVREM_EXTRA_WORDS; + VALUE t = bignew_1(0, tn, 1); + BDIGIT *tds = BDIGITS(t); + tn = BIGNUM_LEN(t); + + /* t = n/x */ + while (bary_divmod_branch(tds, tn, NULL, 0, nds, len, xds, xn), + bary_cmp(tds, tn, xds, xn) < 0) { + int carry; + BARY_TRUNC(tds, tn); + /* x = (x+t)/2 */ + carry = bary_add(xds, xn, xds, xn, tds, tn); + bary_small_rshift(xds, xds, xn, 1, carry); + tn = BIGNUM_LEN(t); + } } RBASIC_SET_CLASS_RAW(x, rb_cInteger); return x; } -#ifdef USE_GMP +#if USE_GMP static void bary_powm_gmp(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t xn, const BDIGIT *yds, size_t yn, const BDIGIT *mds, size_t mn) { - const size_t nails = (sizeof(BDIGIT)-SIZEOF_BDIGIT)*CHAR_BIT; mpz_t z, x, y, m; size_t count; mpz_init(x); mpz_init(y); mpz_init(m); mpz_init(z); - mpz_import(x, xn, -1, sizeof(BDIGIT), 0, nails, xds); - mpz_import(y, yn, -1, sizeof(BDIGIT), 0, nails, yds); - mpz_import(m, mn, -1, sizeof(BDIGIT), 0, nails, mds); + bdigits_to_mpz(x, xds, xn); + bdigits_to_mpz(y, yds, yn); + bdigits_to_mpz(m, mds, mn); mpz_powm(z, x, y, m); - mpz_export(zds, &count, -1, sizeof(BDIGIT), 0, nails, z); + bdigits_from_mpz(z, zds, &count); BDIGITS_ZERO(zds+count, zn-count); mpz_clear(x); mpz_clear(y); @@ -6951,7 +6986,7 @@ bary_powm_gmp(BDIGIT *zds, size_t zn, const BDIGIT *xds, size_t xn, const BDIGIT static VALUE int_pow_tmp3(VALUE x, VALUE y, VALUE m, int nega_flg) { -#ifdef USE_GMP +#if USE_GMP VALUE z; size_t xn, yn, mn, zn; @@ -7117,6 +7152,7 @@ rb_int_powm(int const argc, VALUE * const argv, VALUE const num) long const half_val = (long)HALF_LONG_MSB; long const mm = FIX2LONG(m); if (!mm) rb_num_zerodiv(); + if (mm == 1) return INT2FIX(0); if (mm <= half_val) { return int_pow_tmp1(rb_int_modulo(a, m), b, mm, nega_flg); } @@ -7126,6 +7162,7 @@ rb_int_powm(int const argc, VALUE * const argv, VALUE const num) } else { if (rb_bigzero_p(m)) rb_num_zerodiv(); + if (bignorm(m) == INT2FIX(1)) return INT2FIX(0); return int_pow_tmp3(rb_int_modulo(a, m), b, m, nega_flg); } } @@ -7153,16 +7190,9 @@ rb_int_powm(int const argc, VALUE * const argv, VALUE const num) void Init_Bignum(void) { -#ifndef RUBY_INTEGER_UNIFICATION - rb_cBignum = rb_cInteger; -#endif - /* An obsolete class, use Integer */ - rb_define_const(rb_cObject, "Bignum", rb_cInteger); - rb_deprecate_constant(rb_cObject, "Bignum"); - rb_define_method(rb_cInteger, "coerce", rb_int_coerce, 1); -#ifdef USE_GMP +#if USE_GMP /* The version of loaded GMP. */ rb_define_const(rb_cInteger, "GMP_VERSION", rb_sprintf("GMP %s", gmp_version)); #endif diff --git a/bin/bundle b/bin/bundle deleted file mode 100755 index 1a0b06b005..0000000000 --- a/bin/bundle +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env ruby -# -# This file was generated by RubyGems. -# -# The application 'bundler' is installed as part of a gem, and -# this file is here to facilitate running it. -# - -require 'rubygems' - -version = ">= 0.a" - -str = ARGV.first -if str - str = str.b[/\A_(.*)_\z/, 1] - if str and Gem::Version.correct?(str) - version = str - ARGV.shift - end -end - -if Gem.respond_to?(:activate_bin_path) -load Gem.activate_bin_path('bundler', 'bundle', version) -else -gem "bundler", version -load Gem.bin_path("bundler", "bundle", version) -end diff --git a/bin/bundler b/bin/bundler deleted file mode 100755 index e15eb39ed7..0000000000 --- a/bin/bundler +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env ruby -# -# This file was generated by RubyGems. -# -# The application 'bundler' is installed as part of a gem, and -# this file is here to facilitate running it. -# - -require 'rubygems' - -version = ">= 0.a" - -str = ARGV.first -if str - str = str.b[/\A_(.*)_\z/, 1] - if str and Gem::Version.correct?(str) - version = str - ARGV.shift - end -end - -if Gem.respond_to?(:activate_bin_path) -load Gem.activate_bin_path('bundler', 'bundler', version) -else -gem "bundler", version -load Gem.bin_path("bundler", "bundler", version) -end diff --git a/bin/erb b/bin/erb deleted file mode 100755 index d5e51ee969..0000000000 --- a/bin/erb +++ /dev/null @@ -1,176 +0,0 @@ -#!/usr/bin/env ruby -# Tiny eRuby --- ERB2 -# Copyright (c) 1999-2000,2002 Masatoshi SEKI -# You can redistribute it and/or modify it under the same terms as Ruby. - -require 'erb' - -class ERB - module Main - def ARGV.switch - return nil if self.empty? - arg = self.shift - return nil if arg == '--' - case arg - when /\A-(.)(.*)/ - if $1 == '-' - arg, @maybe_arg = arg.split(/=/, 2) - return arg - end - raise 'unknown switch "-"' if $2[0] == ?- and $1 != 'T' - if $2.size > 0 - self.unshift "-#{$2}" - @maybe_arg = $2 - else - @maybe_arg = nil - end - "-#{$1}" - when /\A(\w+)=/ - arg - else - self.unshift arg - nil - end - end - - def ARGV.req_arg - (@maybe_arg || self.shift || raise('missing argument')).tap { - @maybe_arg = nil - } - end - - def trim_mode_opt(trim_mode, disable_percent) - return trim_mode if disable_percent - case trim_mode - when 0 - return '%' - when 1 - return '%>' - when 2 - return '%<>' - when '-' - return '%-' - end - end - module_function :trim_mode_opt - - def run(factory=ERB) - trim_mode = 0 - disable_percent = false - variables = {} - begin - while switch = ARGV.switch - case switch - when '-x' # ruby source - output = true - when '-n' # line number - number = true - when '-v' # verbose - $VERBOSE = true - when '--version' # version - STDERR.puts factory.version - exit - when '-d', '--debug' # debug - $DEBUG = true - when '-r' # require - require ARGV.req_arg - when '-S' # security level - warn 'warning: -S option of erb command is deprecated. Please do not use this.' - arg = ARGV.req_arg - raise "invalid safe_level #{arg.dump}" unless arg =~ /\A[0-1]\z/ - safe_level = arg.to_i - when '-T' # trim mode - arg = ARGV.req_arg - if arg == '-' - trim_mode = arg - next - end - raise "invalid trim mode #{arg.dump}" unless arg =~ /\A[0-2]\z/ - trim_mode = arg.to_i - when '-E', '--encoding' - arg = ARGV.req_arg - set_encoding(*arg.split(/:/, 2)) - when '-U' - set_encoding(Encoding::UTF_8, Encoding::UTF_8) - when '-P' - disable_percent = true - when '--help' - raise "print this help" - when /\A-/ - raise "unknown switch #{switch.dump}" - else - var, val = *switch.split('=', 2) - (variables ||= {})[var] = val - end - end - rescue # usage - STDERR.puts $!.to_s - STDERR.puts File.basename($0) + - " [switches] [var=value...] [inputfile]" - STDERR.puts <<EOU - -x print ruby script - -n print ruby script with line number - -v enable verbose mode - -d set $DEBUG to true - -r library load a library - -E ex[:in] set default external/internal encodings - -U set default encoding to UTF-8 - -T trim_mode specify trim_mode (0..2, -) - -P disable ruby code evaluation for lines beginning with % - var=value set variable -EOU - exit 1 - end - - $<.set_encoding(Encoding::UTF_8, nil) - src = $<.read - filename = $FILENAME - exit 2 unless src - trim = trim_mode_opt(trim_mode, disable_percent) - if safe_level.nil? - erb = factory.new(src.untaint, trim_mode: trim) - else - # [deprecated] This will be removed at Ruby 2.7. - erb = factory.new(src.untaint, safe_level, trim_mode: trim) - end - erb.filename = filename - if output - if number - erb.src.each_line.with_index do |line, l| - puts "%3d %s"%[l+1, line] - end - else - puts erb.src - end - else - bind = TOPLEVEL_BINDING.taint - if variables - enc = erb.encoding - for var, val in variables do - val = val.encode(enc) if val - bind.local_variable_set(var, val) - end - end - erb.run(bind) - end - end - module_function :run - - def set_encoding(extern, intern = nil) - verbose, $VERBOSE = $VERBOSE, nil - Encoding.default_external = extern unless extern.nil? || extern == "" - Encoding.default_internal = intern unless intern.nil? || intern == "" - [$stdin, $stdout, $stderr].each do |io| - io.set_encoding(extern, intern) - end - ensure - $VERBOSE = verbose - end - module_function :set_encoding - class << self; private :set_encoding; end - end -end - -if __FILE__ == $0 - ERB::Main.run -end @@ -5,21 +5,6 @@ # See LICENSE.txt for permissions. #++ -require 'rubygems' -require 'rubygems/gem_runner' -require 'rubygems/exceptions' - -required_version = Gem::Requirement.new ">= 1.8.7" - -unless required_version.satisfied_by? Gem.ruby_version then - abort "Expected Ruby Version #{required_version}, is #{Gem.ruby_version}" -end - -args = ARGV.clone - -begin - Gem::GemRunner.new.run args -rescue Gem::SystemExitException => e - exit e.exit_code -end +require "rubygems/gem_runner" +Gem::GemRunner.new.run ARGV.clone diff --git a/bin/irb b/bin/irb deleted file mode 100755 index ae6d358c9d..0000000000 --- a/bin/irb +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env ruby -# -# This file was generated by RubyGems. -# -# The application 'irb' is installed as part of a gem, and -# this file is here to facilitate running it. -# - -require 'rubygems' - -version = ">= 0.a" - -str = ARGV.first -if str - str = str.b[/\A_(.*)_\z/, 1] - if str and Gem::Version.correct?(str) - version = str - ARGV.shift - end -end - -if Gem.respond_to?(:activate_bin_path) -load Gem.activate_bin_path('irb', 'irb', version) -else -gem "irb", version -load Gem.bin_path("irb", "irb", version) -end diff --git a/bin/rdoc b/bin/rdoc deleted file mode 100755 index 8fa948cddb..0000000000 --- a/bin/rdoc +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env ruby -# -# This file was generated by RubyGems. -# -# The application 'rdoc' is installed as part of a gem, and -# this file is here to facilitate running it. -# - -require 'rubygems' - -version = ">= 0.a" - -str = ARGV.first -if str - str = str.b[/\A_(.*)_\z/, 1] - if str and Gem::Version.correct?(str) - version = str - ARGV.shift - end -end - -if Gem.respond_to?(:activate_bin_path) -load Gem.activate_bin_path('rdoc', 'rdoc', version) -else -gem "rdoc", version -load Gem.bin_path("rdoc", "rdoc", version) -end diff --git a/bin/ri b/bin/ri deleted file mode 100755 index 0cc2f73bb6..0000000000 --- a/bin/ri +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env ruby -# -# This file was generated by RubyGems. -# -# The application 'rdoc' is installed as part of a gem, and -# this file is here to facilitate running it. -# - -require 'rubygems' - -version = ">= 0.a" - -str = ARGV.first -if str - str = str.b[/\A_(.*)_\z/, 1] - if str and Gem::Version.correct?(str) - version = str - ARGV.shift - end -end - -if Gem.respond_to?(:activate_bin_path) -load Gem.activate_bin_path('rdoc', 'ri', version) -else -gem "rdoc", version -load Gem.bin_path("rdoc", "ri", version) -end diff --git a/bootstraptest/pending.rb b/bootstraptest/pending.rb index 744c0adac1..2c4b85a419 100644 --- a/bootstraptest/pending.rb +++ b/bootstraptest/pending.rb @@ -1,19 +1,3 @@ -assert_equal 'A', %q{ - class A - @@a = 'A' - def a=(x) - @@a = x - end - def a - @@a - end - end - - B = A.dup - B.new.a = 'B' - A.new.a -}, '[ruby-core:17019]' - assert_equal 'ok', %q{ def m lambda{ @@ -31,9 +15,7 @@ assert_equal 'ok', %q{ end } -assert_normal_exit %q{ - r = Range.allocate - def r.<=>(o) true end - r.instance_eval { initialize r, r } - r.inspect +# This randomly fails on mswin. +assert_equal %q{[]}, %q{ + Thread.new{sleep}.backtrace } diff --git a/bootstraptest/runner.rb b/bootstraptest/runner.rb index 58619fae7e..f9b3e919b8 100755 --- a/bootstraptest/runner.rb +++ b/bootstraptest/runner.rb @@ -1,4 +1,4 @@ -"exec" "${RUBY-ruby}" "-x" "$0" "$@" || true # -*- mode: ruby; coding: utf-8 -*- +"exec" "${RUBY-ruby}" "-x" "$0" "$@" || true # -*- Ruby -*- #!./ruby # $Id$ @@ -8,6 +8,8 @@ # Never use Ruby extensions in this file. # Maintain Ruby 1.8 compatibility for now +$start_time = Time.now + begin require 'fileutils' require 'tmpdir' @@ -58,24 +60,114 @@ if !Dir.respond_to?(:mktmpdir) end end +# Configuration +bt = Struct.new(:ruby, + :verbose, + :color, + :tty, + :quiet, + :wn, + :progress, + :progress_bs, + :passed, + :failed, + :reset, + :columns, + :window_width, + :width, + :indent, + :platform, + ) +BT = Class.new(bt) do + def indent=(n) + super + if (self.columns ||= 0) < n + $stderr.print(' ' * (n - self.columns)) + end + self.columns = indent + end + + def putc(c) + unless self.quiet + if self.window_width == nil + unless w = ENV["COLUMNS"] and (w = w.to_i) > 0 + w = 80 + end + w -= 1 + self.window_width = w + end + if self.window_width and self.columns >= self.window_width + $stderr.print "\n", " " * (self.indent ||= 0) + self.columns = indent + end + $stderr.print c + $stderr.flush + self.columns += 1 + end + end + + def wn=(wn) + unless wn == 1 + if /(?:\A|\s)--jobserver-(?:auth|fds)=(?:(\d+),(\d+)|fifo:((?:\\.|\S)+))/ =~ ENV.delete("MAKEFLAGS") + begin + if fifo = $3 + fifo.gsub!(/\\(?=.)/, '') + r = File.open(fifo, IO::RDONLY|IO::NONBLOCK|IO::BINARY) + w = File.open(fifo, IO::WRONLY|IO::NONBLOCK|IO::BINARY) + else + r = IO.for_fd($1.to_i(10), "rb", autoclose: false) + w = IO.for_fd($2.to_i(10), "wb", autoclose: false) + end + rescue => e + r.close if r + else + r.close_on_exec = true + w.close_on_exec = true + tokens = r.read_nonblock(wn > 0 ? wn : 1024, exception: false) + r.close + if String === tokens + tokens.freeze + auth = w + w = nil + at_exit {auth << tokens; auth.close} + wn = tokens.size + 1 + else + w.close + wn = 1 + end + end + end + if wn <= 0 + require 'etc' + wn = [Etc.nprocessors / 2, 1].max + end + end + super wn + end +end.new + +BT_STATE = Struct.new(:count, :error).new + def main - @ruby = File.expand_path('miniruby') - @verbose = false + BT.ruby = File.expand_path('miniruby') + BT.verbose = false $VERBOSE = false $stress = false - @color = nil - @tty = nil - @quiet = false + BT.color = nil + BT.tty = nil + BT.quiet = false + # BT.wn = 1 dir = nil quiet = false tests = nil ARGV.delete_if {|arg| case arg when /\A--ruby=(.*)/ - @ruby = $1 - @ruby.gsub!(/^([^ ]*)/){File.expand_path($1)} - @ruby.gsub!(/(\s+-I\s*)((?!(?:\.\/)*-(?:\s|\z))\S+)/){$1+File.expand_path($2)} - @ruby.gsub!(/(\s+-r\s*)(\.\.?\/\S+)/){$1+File.expand_path($2)} + ruby = $1 + ruby.gsub!(/^([^ ]*)/){File.expand_path($1)} + ruby.gsub!(/(\s+-I\s*)((?!(?:\.\/)*-(?:\s|\z))\S+)/){$1+File.expand_path($2)} + ruby.gsub!(/(\s+-r\s*)(\.\.?\/\S+)/){$1+File.expand_path($2)} + BT.ruby = ruby true when /\A--sets=(.*)/ tests = Dir.glob("#{File.dirname($0)}/test_{#{$1}}*.rb").sort @@ -88,18 +180,23 @@ def main $stress = true when /\A--color(?:=(?:always|(auto)|(never)|(.*)))?\z/ warn "unknown --color argument: #$3" if $3 - @color = $1 ? nil : !$2 + BT.color = color = $1 ? nil : !$2 true when /\A--tty(=(?:yes|(no)|(.*)))?\z/ warn "unknown --tty argument: #$3" if $3 - @tty = !$1 || !$2 + BT.tty = !$1 || !$2 true when /\A(-q|--q(uiet))\z/ quiet = true - @quiet = true + BT.quiet = true + true + when /\A-j(\d+)?/ + BT.wn = $1.to_i true when /\A(-v|--v(erbose))\z/ - @verbose = true + BT.verbose = true + BT.quiet = false + true when /\A(-h|--h(elp)?)\z/ puts(<<-End) Usage: #{File.basename($0, '.*')} --ruby=PATH [--sets=NAME,NAME,...] @@ -121,38 +218,41 @@ End end } if tests and not ARGV.empty? - $stderr.puts "--tests and arguments are exclusive" - exit false + abort "--sets and arguments are exclusive" end tests ||= ARGV tests = Dir.glob("#{File.dirname($0)}/test_*.rb").sort if tests.empty? pathes = tests.map {|path| File.expand_path(path) } - @progress = %w[- \\ | /] - @progress_bs = "\b" * @progress[0].size - @tty = $stderr.tty? if @tty.nil? - case @color + BT.progress = %w[- \\ | /] + BT.progress_bs = "\b" * BT.progress[0].size + BT.tty = $stderr.tty? if BT.tty.nil? + BT.wn ||= /-j(\d+)?/ =~ (ENV["MAKEFLAGS"] || ENV["MFLAGS"]) ? $1.to_i : 1 + + case BT.color when nil - @color = @tty && /dumb/ !~ ENV["TERM"] + BT.color = BT.tty && /dumb/ !~ ENV["TERM"] end - @tty &&= !@verbose - if @color + BT.tty &&= !BT.verbose + if BT.color # dircolors-like style colors = (colors = ENV['TEST_COLORS']) ? Hash[colors.scan(/(\w+)=([^:\n]*)/)] : {} begin - File.read(File.join(__dir__, "../test/colors")).scan(/(\w+)=([^:\n]*)/) do |n, c| + File.read(File.join(__dir__, "../tool/colors")).scan(/(\w+)=([^:\n]*)/) do |n, c| colors[n] ||= c end rescue end - @passed = "\e[;#{colors["pass"] || "32"}m" - @failed = "\e[;#{colors["fail"] || "31"}m" - @reset = "\e[m" + BT.passed = "\e[;#{colors["pass"] || "32"}m" + BT.failed = "\e[;#{colors["fail"] || "31"}m" + BT.reset = "\e[m" else - @passed = @failed = @reset = "" + BT.passed = BT.failed = BT.reset = "" end + target_version = `#{BT.ruby} -v`.chomp + BT.platform = target_version[/\[(.*)\]\z/, 1] unless quiet - puts Time.now + puts $start_time if defined?(RUBY_DESCRIPTION) puts "Driver is #{RUBY_DESCRIPTION}" elsif defined?(RUBY_PATCHLEVEL) @@ -160,254 +260,469 @@ End else puts "Driver is ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]" end - puts "Target is #{`#{@ruby} -v`.chomp}" + puts "Target is #{target_version}" puts $stdout.flush end - in_temporary_working_directory(dir) { + in_temporary_working_directory(dir) do exec_test pathes - } + end end def erase(e = true) - if e and @columns > 0 and !@verbose - "\r#{" "*@columns}\r" + if e and BT.columns > 0 and BT.tty and !BT.verbose + "\e[1K\r" else "" end end -def exec_test(pathes) - @count = 0 - @error = 0 - @errbuf = [] - @location = nil - @columns = 0 - @width = pathes.map {|path| File.basename(path).size}.max + 2 +def load_test pathes pathes.each do |path| - @basename = File.basename(path) - $stderr.printf("%s%-*s ", erase(@quiet), @width, @basename) - $stderr.flush - @columns = @width + 1 - $stderr.puts if @verbose - count = @count - error = @error load File.expand_path(path) - if @tty - if @error == error - msg = "PASS #{@count-count}" - @columns += msg.size - 1 - $stderr.print "#{@progress_bs}#{@passed}#{msg}#{@reset}" + end +end + +def concurrent_exec_test + aq = Queue.new + rq = Queue.new + + ts = BT.wn.times.map do + Thread.new do + while as = aq.pop + as.call + rq << as + end + ensure + rq << nil + end + end + + Assertion.all.to_a.shuffle.each do |path, assertions| + assertions.each do |as| + aq << as + end + end + + BT.indent = 1 + aq.close + i = 1 + term_wn = 0 + begin + while BT.wn != term_wn + if r = rq.pop + case + when BT.quiet + when BT.tty + $stderr.print "#{BT.progress_bs}#{BT.progress[(i+=1) % BT.progress.size]}" + else + BT.putc '.' + end else - msg = "FAIL #{@error-error}/#{@count-count}" - $stderr.print "#{@progress_bs}#{@failed}#{msg}#{@reset}" - @columns = 0 + term_wn += 1 + end + end + ensure + ts.each(&:kill) + ts.each(&:join) + end +end + +def exec_test(pathes) + # setup + load_test pathes + BT_STATE.count = 0 + BT_STATE.error = 0 + BT.columns = 0 + BT.width = pathes.map {|path| File.basename(path).size}.max + 2 + + # execute tests + if BT.wn > 1 + concurrent_exec_test + else + prev_basename = nil + Assertion.all.each do |basename, assertions| + if !BT.quiet && basename != prev_basename + prev_basename = basename + $stderr.printf("%s%-*s ", erase(BT.quiet), BT.width, basename) + $stderr.flush + end + BT.columns = BT.width + 1 + $stderr.puts if BT.verbose + count = BT_STATE.count + error = BT_STATE.error + + assertions.each do |assertion| + BT_STATE.count += 1 + assertion.call + end + + if BT.tty + if BT_STATE.error == error + msg = "PASS #{BT_STATE.count-count}" + BT.columns += msg.size - 1 + $stderr.print "#{BT.progress_bs}#{BT.passed}#{msg}#{BT.reset}" unless BT.quiet + else + msg = "FAIL #{BT_STATE.error-error}/#{BT_STATE.count-count}" + $stderr.print "#{BT.progress_bs}#{BT.failed}#{msg}#{BT.reset}" + BT.columns = 0 + end end + $stderr.puts if !BT.quiet and (BT.tty or BT_STATE.error == error) end - $stderr.puts unless @quiet and @tty and @error == error end - $stderr.print(erase) if @quiet - if @error == 0 - if @count == 0 - $stderr.puts "No tests, no problem" + + # show results + unless BT.quiet + $stderr.puts(erase) + + sec = Time.now - $start_time + $stderr.puts "Finished in #{'%.2f' % sec} sec\n\n" if Assertion.count > 0 + end + + Assertion.errbuf.each do |msg| + $stderr.puts msg + end + + out = BT.quiet ? $stdout : $stderr + + if BT_STATE.error == 0 + if Assertion.count == 0 + out.puts "No tests, no problem" unless BT.quiet else - $stderr.puts "#{@passed}PASS#{@reset} all #{@count} tests" + out.puts "#{BT.passed}PASS#{BT.reset} all #{Assertion.count} tests" end - exit true + true else - @errbuf.each do |msg| - $stderr.puts msg - end - $stderr.puts "#{@failed}FAIL#{@reset} #{@error}/#{@count} tests failed" - exit false + $stderr.puts "#{BT.failed}FAIL#{BT.reset} #{BT_STATE.error}/#{BT_STATE.count} tests failed" + false end end -def show_progress(message = '') - if @verbose - $stderr.print "\##{@count} #{@location} " - elsif @tty - $stderr.print "#{@progress_bs}#{@progress[@count % @progress.size]}" - end - t = Time.now if @verbose - faildesc, errout = with_stderr {yield} - t = Time.now - t if @verbose - if !faildesc - if @tty - $stderr.print "#{@progress_bs}#{@progress[@count % @progress.size]}" - elsif @verbose - $stderr.printf(". %.3f\n", t) +def target_platform + BT.platform or RUBY_PLATFORM +end + +class Assertion < Struct.new(:src, :path, :lineno, :proc) + @count = 0 + @all = Hash.new{|h, k| h[k] = []} + @errbuf = [] + + class << self + attr_reader :count, :errbuf + + def all + @all + end + + def add as + @all[as.path] << as + as.id = (@count += 1) + end + end + + attr_accessor :id + attr_reader :err, :category + + def initialize(*args) + super + self.class.add self + @category = self.path.match(/test_(.+)\.rb/)[1] + end + + def call + self.proc.call self + end + + def assert_check(message = '', opt = '', **argh) + show_progress(message) { + result = get_result_string(opt, **argh) + yield(result) + } + end + + def with_stderr + out = err = nil + r, w = IO.pipe + @err = w + err_reader = Thread.new{ r.read } + + begin + out = yield + ensure + w.close + err = err_reader.value + r.close rescue nil + end + + return out, err + end + + def show_error(msg, additional_message) + msg = "#{BT.failed}\##{self.id} #{self.path}:#{self.lineno}#{BT.reset}: #{msg} #{additional_message}" + if BT.tty + $stderr.puts "#{erase}#{msg}" else - $stderr.print '.' + Assertion.errbuf << msg end - else - $stderr.print "#{@failed}F" - $stderr.printf(" %.3f", t) if @verbose - $stderr.print "#{@reset}" - $stderr.puts if @verbose - error faildesc, message - unless errout.empty? - $stderr.print "#{@failed}stderr output is not empty#{@reset}\n", adjust_indent(errout) + BT_STATE.error += 1 + end + + + def show_progress(message = '') + if BT.quiet || BT.wn > 1 + # do nothing + elsif BT.verbose + $stderr.print "\##{@id} #{self.path}:#{self.lineno} " + elsif BT.tty + $stderr.print "#{BT.progress_bs}#{BT.progress[BT_STATE.count % BT.progress.size]}" + end + + t = Time.now if BT.verbose + faildesc, errout = with_stderr {yield} + t = Time.now - t if BT.verbose + + if !faildesc + # success + if BT.quiet || BT.wn > 1 + # do nothing + elsif BT.tty + $stderr.print "#{BT.progress_bs}#{BT.progress[BT_STATE.count % BT.progress.size]}" + elsif BT.verbose + $stderr.printf(". %.3f\n", t) + else + BT.putc '.' + end + else + $stderr.print "#{BT.failed}F" + $stderr.printf(" %.3f", t) if BT.verbose + $stderr.print BT.reset + $stderr.puts if BT.verbose + show_error faildesc, message + unless errout.empty? + $stderr.print "#{BT.failed}stderr output is not empty#{BT.reset}\n", adjust_indent(errout) + end + + if BT.tty and !BT.verbose and BT.wn == 1 + $stderr.printf("%-*s%s", BT.width, path, BT.progress[BT_STATE.count % BT.progress.size]) + end + end + rescue Interrupt + $stderr.puts "\##{@id} #{path}:#{lineno}" + raise + rescue Exception => err + $stderr.print 'E' + $stderr.puts if BT.verbose + show_error err.message, message + ensure + begin + check_coredump + rescue CoreDumpError => err + $stderr.print 'E' + $stderr.puts if BT.verbose + show_error err.message, message + cleanup_coredump end - if @tty and !@verbose - $stderr.printf("%-*s%s", @width, @basename, @progress[@count % @progress.size]) + end + + def get_result_string(opt = '', **argh) + if BT.ruby + filename = make_srcfile(**argh) + begin + kw = self.err ? {err: self.err} : {} + out = IO.popen("#{BT.ruby} -W0 #{opt} #{filename}", **kw) + pid = out.pid + out.read.tap{ Process.waitpid(pid); out.close } + ensure + raise Interrupt if $? and $?.signaled? && $?.termsig == Signal.list["INT"] + + begin + Process.kill :KILL, pid + rescue Errno::ESRCH + # OK + end + end + else + eval(src).to_s end end -rescue Interrupt - $stderr.puts "\##{@count} #{@location}" - raise -rescue Exception => err - $stderr.print 'E' - $stderr.puts if @verbose - error err.message, message + + def make_srcfile(frozen_string_literal: nil) + filename = "bootstraptest.#{self.path}_#{self.lineno}_#{self.id}.rb" + File.open(filename, 'w') {|f| + f.puts "#frozen_string_literal:true" if frozen_string_literal + f.puts "GC.stress = true" if $stress + f.puts "print(begin; #{self.src}; end)" + } + filename + end end -def assert_check(testsrc, message = '', opt = '', **argh) - show_progress(message) { - result = get_result_string(testsrc, opt, **argh) - check_coredump - yield(result) - } +def add_assertion src, pr + loc = caller_locations(2, 1).first + lineno = loc.lineno + path = File.basename(loc.path) + + Assertion.new(src, path, lineno, pr) end def assert_equal(expected, testsrc, message = '', opt = '', **argh) - newtest - assert_check(testsrc, message, opt, **argh) {|result| - if expected == result - nil - else - desc = "#{result.inspect} (expected #{expected.inspect})" - pretty(testsrc, desc, result) - end - } + add_assertion testsrc, -> as do + as.assert_check(message, opt, **argh) {|result| + if expected == result + nil + else + desc = "#{result.inspect} (expected #{expected.inspect})" + pretty(testsrc, desc, result) + end + } + end end def assert_match(expected_pattern, testsrc, message = '') - newtest - assert_check(testsrc, message) {|result| - if expected_pattern =~ result - nil - else - desc = "#{expected_pattern.inspect} expected to be =~\n#{result.inspect}" - pretty(testsrc, desc, result) - end - } + add_assertion testsrc, -> as do + as.assert_check(message) {|result| + if expected_pattern =~ result + nil + else + desc = "#{expected_pattern.inspect} expected to be =~\n#{result.inspect}" + pretty(testsrc, desc, result) + end + } + end end def assert_not_match(unexpected_pattern, testsrc, message = '') - newtest - assert_check(testsrc, message) {|result| - if unexpected_pattern !~ result - nil - else - desc = "#{unexpected_pattern.inspect} expected to be !~\n#{result.inspect}" - pretty(testsrc, desc, result) - end - } + add_assertion testsrc, -> as do + as.assert_check(message) {|result| + if unexpected_pattern !~ result + nil + else + desc = "#{unexpected_pattern.inspect} expected to be !~\n#{result.inspect}" + pretty(testsrc, desc, result) + end + } + end end def assert_valid_syntax(testsrc, message = '') - newtest - assert_check(testsrc, message, '-c') {|result| - result if /Syntax OK/ !~ result - } + add_assertion testsrc, -> as do + as.assert_check(message, '-c') {|result| + result if /Syntax OK/ !~ result + } + end end def assert_normal_exit(testsrc, *rest, timeout: nil, **opt) - newtest - message, ignore_signals = rest - message ||= '' - show_progress(message) { - faildesc = nil - filename = make_srcfile(testsrc) - old_stderr = $stderr.dup - timeout_signaled = false - begin - $stderr.reopen("assert_normal_exit.log", "w") - io = IO.popen("#{@ruby} -W0 #{filename}") - pid = io.pid - th = Thread.new { - io.read - io.close - $? - } - if !th.join(timeout) - Process.kill :KILL, pid - timeout_signaled = true - end - status = th.value - ensure - $stderr.reopen(old_stderr) - old_stderr.close - end - if status && status.signaled? - signo = status.termsig - signame = Signal.list.invert[signo] - unless ignore_signals and ignore_signals.include?(signame) - sigdesc = "signal #{signo}" - if signame - sigdesc = "SIG#{signame} (#{sigdesc})" - end - if timeout_signaled - sigdesc << " (timeout)" + add_assertion testsrc, -> as do + message, ignore_signals = rest + message ||= '' + as.show_progress(message) { + faildesc = nil + filename = as.make_srcfile + timeout_signaled = false + logfile = "assert_normal_exit.#{as.path}.#{as.lineno}.log" + + begin + err = open(logfile, "w") + io = IO.popen("#{BT.ruby} -W0 #{filename}", err: err) + pid = io.pid + th = Thread.new { + io.read + io.close + $? + } + if !th.join(timeout) + Process.kill :KILL, pid + timeout_signaled = true end - faildesc = pretty(testsrc, "killed by #{sigdesc}", nil) - stderr_log = File.read("assert_normal_exit.log") - if !stderr_log.empty? - faildesc << "\n" if /\n\z/ !~ faildesc - stderr_log << "\n" if /\n\z/ !~ stderr_log - stderr_log.gsub!(/^.*\n/) { '| ' + $& } - faildesc << stderr_log + status = th.value + ensure + err.close + end + if status && status.signaled? + signo = status.termsig + signame = Signal.list.invert[signo] + unless ignore_signals and ignore_signals.include?(signame) + sigdesc = "signal #{signo}" + if signame + sigdesc = "SIG#{signame} (#{sigdesc})" + end + if timeout_signaled + sigdesc << " (timeout)" + end + faildesc = pretty(testsrc, "killed by #{sigdesc}", nil) + stderr_log = File.read(logfile) + if !stderr_log.empty? + faildesc << "\n" if /\n\z/ !~ faildesc + stderr_log << "\n" if /\n\z/ !~ stderr_log + stderr_log.gsub!(/^.*\n/) { '| ' + $& } + faildesc << stderr_log + end end end - end - faildesc - } + faildesc + } + end end def assert_finish(timeout_seconds, testsrc, message = '') - timeout_seconds *= 3 if RubyVM::MJIT.enabled? # for --jit-wait - newtest - show_progress(message) { - faildesc = nil - filename = make_srcfile(testsrc) - io = IO.popen("#{@ruby} -W0 #{filename}") - pid = io.pid - waited = false - tlimit = Time.now + timeout_seconds - diff = timeout_seconds - while diff > 0 - if Process.waitpid pid, Process::WNOHANG - waited = true - break - end - if io.respond_to?(:read_nonblock) - if IO.select([io], nil, nil, diff) - begin - io.read_nonblock(1024) - rescue Errno::EAGAIN, IO::WaitReadable, EOFError - break - end while true + add_assertion testsrc, -> as do + if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # for --jit-wait + timeout_seconds *= 3 + end + + as.show_progress(message) { + faildesc = nil + filename = as.make_srcfile + io = IO.popen("#{BT.ruby} -W0 #{filename}", err: as.err) + pid = io.pid + waited = false + tlimit = Time.now + timeout_seconds + diff = timeout_seconds + while diff > 0 + if Process.waitpid pid, Process::WNOHANG + waited = true + break end - else - sleep 0.1 + if io.respond_to?(:read_nonblock) + if IO.select([io], nil, nil, diff) + begin + io.read_nonblock(1024) + rescue Errno::EAGAIN, IO::WaitReadable, EOFError + break + end while true + end + else + sleep 0.1 + end + diff = tlimit - Time.now end - diff = tlimit - Time.now - end - if !waited - Process.kill(:KILL, pid) - Process.waitpid pid - faildesc = pretty(testsrc, "not finished in #{timeout_seconds} seconds", nil) - end - io.close - faildesc - } + if !waited + Process.kill(:KILL, pid) + Process.waitpid pid + faildesc = pretty(testsrc, "not finished in #{timeout_seconds} seconds", nil) + end + io.close + faildesc + } + end end def flunk(message = '') - newtest - show_progress('') { message } + add_assertion '', -> as do + as.show_progress('') { message } + end +end + +def show_limit(testsrc, opt = '', **argh) + return if BT.quiet + + add_assertion testsrc, -> as do + result = as.get_result_string(opt, **argh) + Assertion.errbuf << result + end end def pretty(src, desc, result) @@ -425,67 +740,6 @@ def untabify(str) str.gsub(/^\t+/) {' ' * (8 * $&.size) } end -def make_srcfile(src, frozen_string_literal: nil) - filename = 'bootstraptest.tmp.rb' - File.open(filename, 'w') {|f| - f.puts "#frozen_string_literal:true" if frozen_string_literal - f.puts "GC.stress = true" if $stress - f.puts "print(begin; #{src}; end)" - } - filename -end - -def get_result_string(src, opt = '', **argh) - if @ruby - filename = make_srcfile(src, **argh) - begin - `#{@ruby} -W0 #{opt} #{filename}` - ensure - raise Interrupt if $? and $?.signaled? && $?.termsig == Signal.list["INT"] - raise CoreDumpError, "core dumped" if $? and $?.coredump? - end - else - eval(src).to_s - end -end - -def with_stderr - out = err = nil - begin - r, w = IO.pipe - stderr = $stderr.dup - $stderr.reopen(w) - w.close - reader = Thread.start {r.read} - begin - out = yield - ensure - $stderr.reopen(stderr) - err = reader.value - end - ensure - w.close rescue nil - r.close rescue nil - end - return out, err -end - -def newtest - @location = File.basename(caller(2).first) - @count += 1 - cleanup_coredump -end - -def error(msg, additional_message) - msg = "#{@failed}\##{@count} #{@location}#{@reset}: #{msg} #{additional_message}" - if @tty - $stderr.puts "#{erase}#{msg}" - else - @errbuf.push msg - end - @error += 1 -end - def in_temporary_working_directory(dir) if dir Dir.mkdir dir @@ -502,18 +756,32 @@ def in_temporary_working_directory(dir) end def cleanup_coredump - FileUtils.rm_f 'core' + if File.file?('core') + require 'time' + Dir.glob('/tmp/bootstraptest-core.*').each do |f| + if Time.now - File.mtime(f) > 7 * 24 * 60 * 60 # 7 days + warn "Deleting an old core file: #{f}" + FileUtils.rm(f) + end + end + core_path = "/tmp/bootstraptest-core.#{Time.now.utc.iso8601}" + warn "A core file is found. Saving it at: #{core_path.dump}" + FileUtils.mv('core', core_path) + cmd = ['gdb', BT.ruby, '-c', core_path, '-ex', 'bt', '-batch'] + p cmd # debugging why it's not working + system(*cmd) + end FileUtils.rm_f Dir.glob('core.*') - FileUtils.rm_f @ruby+'.stackdump' if @ruby + FileUtils.rm_f BT.ruby+'.stackdump' if BT.ruby end class CoreDumpError < StandardError; end def check_coredump if File.file?('core') or not Dir.glob('core.*').empty? or - (@ruby and File.exist?(@ruby+'.stackdump')) + (BT.ruby and File.exist?(BT.ruby+'.stackdump')) raise CoreDumpError, "core dumped" end end -main +exit main diff --git a/bootstraptest/test_attr.rb b/bootstraptest/test_attr.rb index 721a847145..3cb9d3eb39 100644 --- a/bootstraptest/test_attr.rb +++ b/bootstraptest/test_attr.rb @@ -34,3 +34,19 @@ assert_equal %{ok}, %{ print "ok" end }, '[ruby-core:15120]' + +assert_equal %{ok}, %{ + class Big + attr_reader :foo + def initialize + @foo = "ok" + end + end + + obj = Big.new + 100.times do |i| + obj.instance_variable_set(:"@ivar_\#{i}", i) + end + + Big.new.foo +} diff --git a/bootstraptest/test_autoload.rb b/bootstraptest/test_autoload.rb index a9f8e6dacd..9e0850bc52 100644 --- a/bootstraptest/test_autoload.rb +++ b/bootstraptest/test_autoload.rb @@ -1,7 +1,7 @@ assert_equal 'ok', %q{ - File.unlink('zzz.rb') if File.file?('zzz.rb') + File.unlink('zzz1.rb') if File.file?('zzz1.rb') instance_eval do - autoload :ZZZ, './zzz.rb' + autoload :ZZZ, './zzz1.rb' begin ZZZ rescue LoadError @@ -11,9 +11,9 @@ assert_equal 'ok', %q{ }, '[ruby-dev:43816]' assert_equal 'ok', %q{ - open('zzz.rb', 'w') {|f| f.puts '' } + open('zzz2.rb', 'w') {|f| f.puts '' } instance_eval do - autoload :ZZZ, './zzz.rb' + autoload :ZZZ, './zzz2.rb' begin ZZZ rescue NameError @@ -23,29 +23,29 @@ assert_equal 'ok', %q{ }, '[ruby-dev:43816]' assert_equal 'ok', %q{ - open('zzz.rb', 'w') {|f| f.puts 'class ZZZ; def self.ok;:ok;end;end'} + open('zzz3.rb', 'w') {|f| f.puts 'class ZZZ; def self.ok;:ok;end;end'} instance_eval do - autoload :ZZZ, './zzz.rb' + autoload :ZZZ, './zzz3.rb' ZZZ.ok end }, '[ruby-dev:43816]' assert_equal 'ok', %q{ - open("zzz.rb", "w") {|f| f.puts "class ZZZ; def self.ok;:ok;end;end"} - autoload :ZZZ, "./zzz.rb" + open("zzz4.rb", "w") {|f| f.puts "class ZZZ; def self.ok;:ok;end;end"} + autoload :ZZZ, "./zzz4.rb" ZZZ.ok } assert_equal 'ok', %q{ - open("zzz.rb", "w") {|f| f.puts "class ZZZ; def self.ok;:ok;end;end"} - autoload :ZZZ, "./zzz.rb" - require "./zzz.rb" + open("zzz5.rb", "w") {|f| f.puts "class ZZZ; def self.ok;:ok;end;end"} + autoload :ZZZ, "./zzz5.rb" + require "./zzz5.rb" ZZZ.ok } assert_equal 'okok', %q{ - open("zzz.rb", "w") {|f| f.puts "class ZZZ; def self.ok;:ok;end;end"} - autoload :ZZZ, "./zzz.rb" + open("zzz6.rb", "w") {|f| f.puts "class ZZZ; def self.ok;:ok;end;end"} + autoload :ZZZ, "./zzz6.rb" t1 = Thread.new {ZZZ.ok} t2 = Thread.new {ZZZ.ok} [t1.value, t2.value].join @@ -60,9 +60,9 @@ assert_finish 5, %q{ }, '[ruby-core:21696]' assert_equal 'A::C', %q{ - open("zzz.rb", "w") {} + open("zzz7.rb", "w") {} class A - autoload :C, "./zzz" + autoload :C, "./zzz7" class C end C diff --git a/bootstraptest/test_constant_cache.rb b/bootstraptest/test_constant_cache.rb new file mode 100644 index 0000000000..1fa83256ed --- /dev/null +++ b/bootstraptest/test_constant_cache.rb @@ -0,0 +1,187 @@ +# Constant lookup is cached. +assert_equal '1', %q{ + CONST = 1 + + def const + CONST + end + + const + const +} + +# Invalidate when a constant is set. +assert_equal '2', %q{ + CONST = 1 + + def const + CONST + end + + const + + CONST = 2 + + const +} + +# Invalidate when a constant of the same name is set. +assert_equal '1', %q{ + CONST = 1 + + def const + CONST + end + + const + + class Container + CONST = 2 + end + + const +} + +# Invalidate when a constant is removed. +assert_equal 'missing', %q{ + class Container + CONST = 1 + + def const + CONST + end + + def self.const_missing(name) + 'missing' + end + + new.const + remove_const :CONST + end + + Container.new.const +} + +# Invalidate when a constant's visibility changes. +assert_equal 'missing', %q{ + class Container + CONST = 1 + + def self.const_missing(name) + 'missing' + end + end + + def const + Container::CONST + end + + const + + Container.private_constant :CONST + + const +} + +# Invalidate when a constant's visibility changes even if the call to the +# visibility change method fails. +assert_equal 'missing', %q{ + class Container + CONST1 = 1 + + def self.const_missing(name) + 'missing' + end + end + + def const1 + Container::CONST1 + end + + const1 + + begin + Container.private_constant :CONST1, :CONST2 + rescue NameError + end + + const1 +} + +# Invalidate when a module is included. +assert_equal 'INCLUDE', %q{ + module Include + CONST = :INCLUDE + end + + class Parent + CONST = :PARENT + end + + class Child < Parent + def const + CONST + end + + new.const + + include Include + end + + Child.new.const +} + +# Invalidate when const_missing is hit. +assert_equal '2', %q{ + module Container + Foo = 1 + Bar = 2 + + class << self + attr_accessor :count + + def const_missing(name) + @count += 1 + @count == 1 ? Foo : Bar + end + end + + @count = 0 + end + + def const + Container::Baz + end + + const + const +} + +# Invalidate when the iseq gets cleaned up. +assert_equal '2', %q{ + CONSTANT = 1 + + iseq = RubyVM::InstructionSequence.compile(<<~RUBY) + CONSTANT + RUBY + + iseq.eval + iseq = nil + + GC.start + CONSTANT = 2 +} + +# Invalidate when the iseq gets cleaned up even if it was never in the cache. +assert_equal '2', %q{ + CONSTANT = 1 + + iseq = RubyVM::InstructionSequence.compile(<<~RUBY) + CONSTANT + RUBY + + iseq = nil + + GC.start + CONSTANT = 2 +} diff --git a/bootstraptest/test_eval.rb b/bootstraptest/test_eval.rb index 8e90ac2728..a9f389c673 100644 --- a/bootstraptest/test_eval.rb +++ b/bootstraptest/test_eval.rb @@ -116,6 +116,33 @@ assert_equal %q{1}, %q{ Const } } +assert_equal %q{1}, %q{ + class TrueClass + Const = 1 + end + true.instance_eval %{ + Const + } +} +assert_equal %q{[:Const]}, %q{ + mod = Module.new + mod.instance_eval %{ + Const = 1 + } + raise if defined?(Module::Const) + mod.singleton_class.constants +} +assert_equal %q{can't define singleton}, %q{ + begin + 123.instance_eval %{ + Const = 1 + } + "bad" + rescue TypeError => e + raise "bad" if defined?(Integer::Const) + e.message + end +} assert_equal %q{top}, %q{ Const = :top class C @@ -190,7 +217,8 @@ assert_equal %q{[10, main]}, %q{ } %w[break next redo].each do |keyword| - assert_match %r"Can't escape from eval with #{keyword}\z", %{ + assert_match %r"Can't escape from eval with #{keyword}\b", %{ + $stderr = STDOUT begin eval "0 rescue #{keyword}" rescue SyntaxError => e @@ -200,7 +228,7 @@ assert_equal %q{[10, main]}, %q{ end assert_normal_exit %q{ - STDERR.reopen(STDOUT) + $stderr = STDOUT class Foo def self.add_method class_eval("def some-bad-name; puts 'hello' unless @some_variable.some_function(''); end") @@ -249,7 +277,9 @@ assert_equal 'ok', %q{ assert_equal 'ok', %q{ begin - 12.instance_eval { @@a } + class A + 12.instance_eval { @@a } + end rescue NameError :ok end @@ -257,7 +287,9 @@ assert_equal 'ok', %q{ assert_equal 'ok', %q{ begin - 12.instance_exec { @@a } + class A + 12.instance_exec { @@a } + end rescue NameError :ok end diff --git a/bootstraptest/test_fiber.rb b/bootstraptest/test_fiber.rb new file mode 100644 index 0000000000..2614dd13bf --- /dev/null +++ b/bootstraptest/test_fiber.rb @@ -0,0 +1,39 @@ +show_limit %q{ + fibers = [] + begin + fiber = Fiber.new{Fiber.yield} + fiber.resume + fibers << fiber + + raise Exception, "skipping" if fibers.count >= 10_000 + rescue Exception => error + puts "Fiber count: #{fibers.count} (#{error})" + break + end while true +} + +assert_equal %q{ok}, %q{ + Fiber.new{ + }.resume + :ok +} + +assert_equal %q{ok}, %q{ + 100.times.collect{Fiber.new{}} + :ok +} + +assert_equal %q{ok}, %q{ + fibers = 1000.times.collect{Fiber.new{Fiber.yield}} + fibers.each(&:resume) + fibers.each(&:resume) + :ok +} + +assert_normal_exit %q{ + at_exit { Fiber.new{}.resume } +} + +assert_normal_exit %q{ + Fiber.new(&Object.method(:class_eval)).resume("foo") +}, '[ruby-dev:34128]' diff --git a/bootstraptest/test_flow.rb b/bootstraptest/test_flow.rb index 9da6d45cbd..35f19db588 100644 --- a/bootstraptest/test_flow.rb +++ b/bootstraptest/test_flow.rb @@ -534,11 +534,11 @@ assert_equal %Q{ENSURE\n}, %q{ ['[ruby-core:39125]', %q{ class Bug5234 include Enumerable - def each + def each(&block) begin yield :foo ensure - proc + proc(&block) end end end @@ -547,11 +547,11 @@ assert_equal %Q{ENSURE\n}, %q{ ['[ruby-dev:45656]', %q{ class Bug6460 include Enumerable - def each + def each(&block) begin yield :foo ensure - 1.times { Proc.new } + 1.times { Proc.new(&block) } end end end diff --git a/bootstraptest/test_insns.rb b/bootstraptest/test_insns.rb index 88f36bd0c2..91fba9b011 100644 --- a/bootstraptest/test_insns.rb +++ b/bootstraptest/test_insns.rb @@ -10,8 +10,10 @@ begin rescue LoadError # OK, just skip else - $FIXNUM_MAX = RbConfig::LIMITS["FIXNUM_MAX"] - $FIXNUM_MIN = RbConfig::LIMITS["FIXNUM_MIN"] + if defined? RbConfig::LIMITS + $FIXNUM_MAX = RbConfig::LIMITS["FIXNUM_MAX"] + $FIXNUM_MIN = RbConfig::LIMITS["FIXNUM_MIN"] + end end fsl = { frozen_string_literal: true } # used later @@ -26,20 +28,20 @@ tests = [ [ 'getlocal *, 1', %q{ x = true; -> { x }.call }, ], [ 'getlocal', %q{ x = true; -> { -> { x }.() }.() }, ], - [ 'setblockparam', <<~'},', ], # { + [ 'setblockparam', <<-'},', ], # { def m&b b = # here proc { true } end m { false }.call }, - [ 'getblockparam', <<~'},', ], # { + [ 'getblockparam', <<-'},', ], # { def m&b b # here end m { true }.call }, - [ 'getblockparamproxy', <<~'},', ], # { + [ 'getblockparamproxy', <<-'},', ], # { def m&b b # here .call @@ -62,8 +64,8 @@ tests = [ [ 'setinstancevariable', %q{ @x = true }, ], [ 'getinstancevariable', %q{ @x = true; @x }, ], - [ 'setclassvariable', %q{ @@x = true }, ], - [ 'getclassvariable', %q{ @@x = true; @@x }, ], + [ 'setclassvariable', %q{ class A; @@x = true; end }, ], + [ 'getclassvariable', %q{ class A; @@x = true; @@x end }, ], [ 'setconstant', %q{ X = true }, ], [ 'setconstant', %q{ Object::X = true }, ], @@ -84,26 +86,24 @@ tests = [ [ 'putobject', %q{ /(?<x>x)/ =~ "x"; x == "x" }, ], [ 'putspecialobject', %q{ {//=>true}[//] }, ], - [ 'putiseq', %q{ -> { true }.() }, ], [ 'putstring', %q{ "true" }, ], [ 'tostring / concatstrings', %q{ "#{true}" }, ], - [ 'freezestring', %q{ "#{true}" }, fsl, ], - [ 'freezestring', %q{ "#{true}" }, '-d', fsl, ], [ 'toregexp', %q{ /#{true}/ =~ "true" && $~ }, ], [ 'intern', %q{ :"#{true}" }, ], [ 'newarray', %q{ ["true"][0] }, ], + [ 'newarraykwsplat', %q{ [**{x:'true'}][0][:x] }, ], [ 'duparray', %q{ [ true ][0] }, ], [ 'expandarray', %q{ y = [ true, false, nil ]; x, = y; x }, ], [ 'expandarray', %q{ y = [ true, false, nil ]; x, *z = y; x }, ], [ 'expandarray', %q{ y = [ true, false, nil ]; x, *z, w = y; x }, ], [ 'splatarray', %q{ x, = *(y = true), false; x }, ], [ 'concatarray', %q{ ["t", "r", *x = "u", "e"].join }, ], - [ 'concatarray', <<~'},', ], # { + [ 'concatarray', <<-'},', ], # { class X; def to_a; ['u']; end; end ['t', 'r', *X.new, 'e'].join }, - [ 'concatarray', <<~'},', ], # { + [ 'concatarray', <<-'},', ], # { r = false t = [true, nil] q, w, e = r, *t # here @@ -120,7 +120,8 @@ tests = [ [ 'dup', %q{ x = y = true; x }, ], [ 'dupn', %q{ Object::X ||= true }, ], [ 'reverse', %q{ q, (w, e), r = 1, [2, 3], 4; e == 3 }, ], - [ 'swap', <<~'},', ], # { + [ 'swap', %q{ !!defined?([[]]) }, ], + [ 'swap', <<-'},', ], # { x = [[false, true]] for i, j in x # here ; @@ -135,7 +136,7 @@ tests = [ [ 'defined', %q{ !defined?(x) }, ], [ 'checkkeyword', %q{ def x x:rand;x end; x x: true }, ], [ 'checktype', %q{ x = true; "#{x}" }, ], - [ 'checkmatch', <<~'},', ], # { + [ 'checkmatch', <<-'},', ], # { x = y = true case x when false @@ -145,7 +146,7 @@ tests = [ end y == nil }, - [ 'checkmatch', <<~'},', ], # { + [ 'checkmatch', <<-'},', ], # { x, y = true, [false] case x when *y # here @@ -155,7 +156,7 @@ tests = [ end z }, - [ 'checkmatch', <<~'},', ], # { + [ 'checkmatch', <<-'},', ], # { x = false begin raise @@ -171,7 +172,7 @@ tests = [ [ 'defineclass', %q{ X = Class.new; class X; true end }, ], [ 'defineclass', %q{ X = Class.new; class Y < X; true end }, ], [ 'defineclass', %q{ X = Class.new; class << X; true end }, ], - [ 'defineclass', <<~'},', ], # { + [ 'defineclass', <<-'},', ], # { X = Class.new Y = Class.new(X) class Y < X @@ -182,7 +183,7 @@ tests = [ [ 'opt_send_without_block', %q{ true.to_s }, ], [ 'send', %q{ true.tap {|i| i.to_s } }, ], [ 'leave', %q{ def x; true; end; x }, ], - [ 'invokesuper', <<~'},', ], # { + [ 'invokesuper', <<-'},', ], # { class X < String def empty? super # here @@ -190,7 +191,7 @@ tests = [ end X.new.empty? }, - [ 'invokeblock', <<~'},', ], # { + [ 'invokeblock', <<-'},', ], # { def x return yield self # here end @@ -200,8 +201,11 @@ tests = [ }, [ 'opt_str_freeze', %q{ 'true'.freeze }, ], + [ 'opt_nil_p', %q{ nil.nil? }, ], + [ 'opt_nil_p', %q{ !Object.nil? }, ], + [ 'opt_nil_p', %q{ Class.new{def nil?; true end}.new.nil? }, ], [ 'opt_str_uminus', %q{ -'true' }, ], - [ 'opt_str_freeze', <<~'},', ], # { + [ 'opt_str_freeze', <<-'},', ], # { class String def freeze true @@ -212,7 +216,7 @@ tests = [ [ 'opt_newarray_max', %q{ [ ].max.nil? }, ], [ 'opt_newarray_max', %q{ [1, x = 2, 3].max == 3 }, ], - [ 'opt_newarray_max', <<~'},', ], # { + [ 'opt_newarray_max', <<-'},', ], # { class Array def max true @@ -222,7 +226,7 @@ tests = [ }, [ 'opt_newarray_min', %q{ [ ].min.nil? }, ], [ 'opt_newarray_min', %q{ [3, x = 2, 1].min == 1 }, ], - [ 'opt_newarray_min', <<~'},', ], # { + [ 'opt_newarray_min', <<-'},', ], # { class Array def min true @@ -238,12 +242,12 @@ tests = [ [ 'branchunless', %q{ x = nil; x &&= true; x.nil? }, ], [ 'branchnil', %q{ x = true; x&.to_s }, ], [ 'branchnil', %q{ x = nil; (x&.to_s).nil? }, ], - [ 'jump', <<~'},', ], # { + [ 'jump', <<-'},', ], # { y = 1 x = if y == 0 then nil elsif y == 1 then true else nil end x }, - [ 'jump', <<~'},', ], # { + [ 'jump', <<-'},', ], # { # ultra complicated situation: this ||= assignment only generates # 15 instructions, not including the class definition. class X; attr_accessor :x; end @@ -252,14 +256,14 @@ tests = [ }, [ 'once', %q{ /#{true}/o =~ "true" && $~ }, ], - [ 'once', <<~'},', ], # { + [ 'once', <<-'},', ], # { def once expr return /#{expr}/o # here end x = once(true); x = once(false); x = once(nil); x =~ "true" && $~ }, - [ 'once', <<~'},', ], # { + [ 'once', <<-'},', ], # { # recursive once def once n return %r/#{ @@ -273,7 +277,7 @@ tests = [ x = once(128); x = once(7); x = once(16); x =~ "true" && $~ }, - [ 'once', <<~'},', ], # { + [ 'once', <<-'},', ], # { # inter-thread lockup situation def once n return Thread.start n do |m| @@ -321,12 +325,12 @@ tests = [ [ 'opt_mod', %q{ '%s' % [ true ] }, ], [ 'opt_eq', %q{ 1 == 1 }, ], - [ 'opt_eq', <<~'},', ], # { + [ 'opt_eq', <<-'},', ], # { class X; def == other; true; end; end X.new == true }, [ 'opt_neq', %q{ 1 != 0 }, ], - [ 'opt_neq', <<~'},', ], # { + [ 'opt_neq', <<-'},', ], # { class X; def != other; true; end; end X.new != true }, @@ -358,7 +362,7 @@ tests = [ [ 'opt_aset', %q{ [][0] = true }, ], [ 'opt_aset', %q{ {}[0] = true }, ], [ 'opt_aset', %q{ x = 'frue'; x[0] = 't'; x }, ], - [ 'opt_aset', <<~'},', ], # { + [ 'opt_aset', <<-'},', ], # { # opt_aref / opt_aset mixup situation class X; def x; {}; end; end x = X.new @@ -381,40 +385,56 @@ tests = [ [ 'opt_empty_p', %q{ ''.empty? }, ], [ 'opt_empty_p', %q{ [].empty? }, ], [ 'opt_empty_p', %q{ {}.empty? }, ], - [ 'opt_empty_p', %q{ Queue.new.empty? }, ], + [ 'opt_empty_p', %q{ Thread::Queue.new.empty? }, ], [ 'opt_succ', %q{ 1.succ == 2 }, ], if defined? $FIXNUM_MAX then [ 'opt_succ',%Q{ #{ $FIXNUM_MAX }.succ == #{ $FIXNUM_MAX + 1 } }, ] end, [ 'opt_succ', %q{ '1'.succ == '2' }, ], - [ 'opt_succ', %q{ x = Time.at(0); x.succ == Time.at(1) }, ], [ 'opt_not', %q{ ! false }, ], - [ 'opt_neq', <<~'},', ], # { + [ 'opt_neq', <<-'},', ], # { class X; def !; true; end; end ! X.new }, - [ 'opt_regexpmatch1', %q{ /true/ =~ 'true' && $~ }, ], - [ 'opt_regexpmatch1', <<~'},', ], # { + [ 'opt_regexpmatch2', %q{ /true/ =~ 'true' && $~ }, ], + [ 'opt_regexpmatch2', <<-'},', ], # { class Regexp; def =~ other; true; end; end /true/ =~ 'true' }, [ 'opt_regexpmatch2', %q{ 'true' =~ /true/ && $~ }, ], - [ 'opt_regexpmatch2', <<~'},', ], # { + [ 'opt_regexpmatch2', <<-'},', ], # { class String; def =~ other; true; end; end 'true' =~ /true/ }, - - [ 'opt_call_c_function', 'Struct.new(:x).new.x = true', ], ] # normal path -tests.compact.each {|(insn, expr, *a)| assert_equal 'true', expr, insn, *a } +tests.compact.each do |(insn, expr, *a)| + if a.last.is_a?(Hash) + a = a.dup + kw = a.pop + assert_equal 'true', expr, insn, *a, **kw + else + assert_equal 'true', expr, insn, *a + end +end # with trace tests.compact.each {|(insn, expr, *a)| progn = "set_trace_func(proc{})\n" + expr - assert_equal 'true', progn, insn, *a + if a.last.is_a?(Hash) + a = a.dup + kw = a.pop + assert_equal 'true', progn, 'trace_' + insn, *a, **kw + else + assert_equal 'true', progn, 'trace_' + insn, *a + end } + +assert_normal_exit("#{<<-"begin;"}\n#{<<-'end;'}") +begin; + RubyVM::InstructionSequence.compile("", debug_level: 5) +end; diff --git a/bootstraptest/test_io.rb b/bootstraptest/test_io.rb index 89c00d0b88..666e5a011b 100644 --- a/bootstraptest/test_io.rb +++ b/bootstraptest/test_io.rb @@ -1,3 +1,4 @@ +/freebsd/ =~ RUBY_PLATFORM or assert_finish 5, %q{ r, w = IO.pipe t1 = Thread.new { r.sysread(1) } @@ -30,7 +31,8 @@ assert_finish 10, %q{ end }, '[ruby-dev:32566]' -assert_finish 1, %q{ +/freebsd/ =~ RUBY_PLATFORM or +assert_finish 5, %q{ r, w = IO.pipe Thread.new { w << "ab" @@ -83,6 +85,7 @@ assert_normal_exit %q{ ARGF.set_encoding "foo" } +/freebsd/ =~ RUBY_PLATFORM or 10.times do assert_normal_exit %q{ at_exit { p :foo } diff --git a/bootstraptest/test_jump.rb b/bootstraptest/test_jump.rb index 595aaa7c4b..d07c47a56d 100644 --- a/bootstraptest/test_jump.rb +++ b/bootstraptest/test_jump.rb @@ -146,15 +146,16 @@ assert_equal %q{131}, %q{ end } } -assert_equal %q{ok}, %q{ +assert_match %r{Invalid retry}, %q{ +$stderr = STDOUT begin eval %q{ 1.times{ retry } } -rescue SyntaxError - :ok +rescue SyntaxError => e + e.message end } assert_equal %q{3}, %q{ @@ -295,14 +296,19 @@ assert_equal "true", %q{ s.return_eigenclass == class << s; self; end }, '[ruby-core:21379]' -assert_equal "true", %q{ - class Object - def yield_eigenclass - class << self - yield self +assert_match %r{Invalid yield}, %q{ +$stderr = STDOUT +begin + eval %q{ + class Object + def yield_eigenclass + class << self + yield self + end end end - end - s = "foo" - s.yield_eigenclass {|c| c == class << s; self; end } -}, '[ruby-dev:40975]' + } +rescue SyntaxError => e + e.message +end +} diff --git a/bootstraptest/test_literal.rb b/bootstraptest/test_literal.rb index 9b3c10d519..a0d4ee08c6 100644 --- a/bootstraptest/test_literal.rb +++ b/bootstraptest/test_literal.rb @@ -65,8 +65,11 @@ assert_equal ':a3c', ':"a#{1+2}c".inspect' assert_equal 'Symbol', ':"a#{1+2}c".class' # xstring -assert_equal "foo\n", %q(`echo foo`) -assert_equal "foo\n", %q(s = "foo"; `echo #{s}`) +# WASI doesn't support spawning a new process for now. +if /wasi/ !~ target_platform + assert_equal "foo\n", %q(`echo foo`) + assert_equal "foo\n", %q(s = "foo"; `echo #{s}`) +end # regexp assert_equal '', '//.source' diff --git a/bootstraptest/test_literal_suffix.rb b/bootstraptest/test_literal_suffix.rb index 4316c9e040..c36fa7078f 100644 --- a/bootstraptest/test_literal_suffix.rb +++ b/bootstraptest/test_literal_suffix.rb @@ -46,9 +46,9 @@ assert_equal '1', '1rescue nil' assert_equal '10000000000000000001/10000000000000000000', '1.0000000000000000001r' -assert_equal 'syntax error, unexpected tIDENTIFIER, expecting end-of-input', +assert_equal 'syntax error, unexpected local variable or method, expecting end-of-input', %q{begin eval('1ir', nil, '', 0); rescue SyntaxError => e; e.message[/\A:(?:\d+:)? (.*)/, 1] end} -assert_equal 'syntax error, unexpected tIDENTIFIER, expecting end-of-input', +assert_equal 'syntax error, unexpected local variable or method, expecting end-of-input', %q{begin eval('1.2ir', nil, '', 0); rescue SyntaxError => e; e.message[/\A:(?:\d+:)? (.*)/, 1] end} -assert_equal 'syntax error, unexpected tIDENTIFIER, expecting end-of-input', +assert_equal 'syntax error, unexpected local variable or method, expecting end-of-input', %q{begin eval('1e1r', nil, '', 0); rescue SyntaxError => e; e.message[/\A:(?:\d+:)? (.*)/, 1] end} diff --git a/bootstraptest/test_method.rb b/bootstraptest/test_method.rb index 3462aa9434..04c9eb2d11 100644 --- a/bootstraptest/test_method.rb +++ b/bootstraptest/test_method.rb @@ -22,7 +22,7 @@ assert_match /\Awrong number of arguments \(.*\b0\b.* 1\)\z/, %q{ } # default argument -assert_equal '1', 'def m(x=1) x end; m()' +assert_equal '1', 'def m(x=1) x end; m();' assert_equal '1', 'def m(x=7) x end; m(1)' assert_equal '1', 'def m(a,x=1) x end; m(7)' assert_equal '1', 'def m(a,x=7) x end; m(7,1)' diff --git a/bootstraptest/test_objectspace.rb b/bootstraptest/test_objectspace.rb index 24a1a0ce2c..63a8d99322 100644 --- a/bootstraptest/test_objectspace.rb +++ b/bootstraptest/test_objectspace.rb @@ -44,3 +44,12 @@ assert_normal_exit %q{ Thread.new {} end }, '[ruby-core:37858]' + +assert_equal 'ok', %q{ + objects_and_ids = 1000.times.map { o = Object.new; [o, o.object_id] } + objects_and_ids.each { |expected, id| + actual = ObjectSpace._id2ref(id) + raise "expected #{expected.inspect}, got #{actual.inspect}" unless actual.equal?(expected) + } + 'ok' +} diff --git a/bootstraptest/test_proc.rb b/bootstraptest/test_proc.rb index 1e384411dc..637603243d 100644 --- a/bootstraptest/test_proc.rb +++ b/bootstraptest/test_proc.rb @@ -225,19 +225,6 @@ assert_equal %q{[[nil, []], [1, []], [1, [2]], [1, [2, 3]]]}, %q{ ] } assert_equal %q{1}, %q{ - pr = proc{ - $SAFE - } - $SAFE = 1 - pr.call -} -assert_equal %q{[1, 1]}, %q{ - pr = proc{ - $SAFE += 1 - } - [pr.call, $SAFE] -} -assert_equal %q{1}, %q{ def m(&b) b end @@ -380,8 +367,8 @@ assert_equal 'ok', %q{ assert_equal 'ok', %q{ class Foo - def call_it - p = Proc.new + def call_it(&block) + p = Proc.new(&block) p.call end end diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb new file mode 100644 index 0000000000..67e66b03ee --- /dev/null +++ b/bootstraptest/test_ractor.rb @@ -0,0 +1,1628 @@ +# Ractor.current returns a current ractor +assert_equal 'Ractor', %q{ + Ractor.current.class +} + +# Ractor.new returns new Ractor +assert_equal 'Ractor', %q{ + Ractor.new{}.class +} + +# Ractor.allocate is not supported +assert_equal "[:ok, :ok]", %q{ + rs = [] + begin + Ractor.allocate + rescue => e + rs << :ok if e.message == 'allocator undefined for Ractor' + end + + begin + Ractor.new{}.dup + rescue + rs << :ok if e.message == 'allocator undefined for Ractor' + end + + rs +} + +# A Ractor can have a name +assert_equal 'test-name', %q{ + r = Ractor.new name: 'test-name' do + end + r.name +} + +# If Ractor doesn't have a name, Ractor#name returns nil. +assert_equal 'nil', %q{ + r = Ractor.new do + end + r.name.inspect +} + +# Raises exceptions if initialize with an invalid name +assert_equal 'ok', %q{ + begin + r = Ractor.new(name: [{}]) {} + rescue TypeError => e + 'ok' + end +} + +# Ractor.new must call with a block +assert_equal "must be called with a block", %q{ + begin + Ractor.new + rescue ArgumentError => e + e.message + end +} + +# Ractor#inspect +# Return only id and status for main ractor +assert_equal "#<Ractor:#1 running>", %q{ + Ractor.current.inspect +} + +# Return id, loc, and status for no-name ractor +assert_match /^#<Ractor:#([^ ]*?) .+:[0-9]+ terminated>$/, %q{ + r = Ractor.new { '' } + r.take + sleep 0.1 until r.inspect =~ /terminated/ + r.inspect +} + +# Return id, name, loc, and status for named ractor +assert_match /^#<Ractor:#([^ ]*?) Test Ractor .+:[0-9]+ terminated>$/, %q{ + r = Ractor.new(name: 'Test Ractor') { '' } + r.take + sleep 0.1 until r.inspect =~ /terminated/ + r.inspect +} + +# A return value of a Ractor block will be a message from the Ractor. +assert_equal 'ok', %q{ + # join + r = Ractor.new do + 'ok' + end + r.take +} + +# Passed arguments to Ractor.new will be a block parameter +# The values are passed with Ractor-communication pass. +assert_equal 'ok', %q{ + # ping-pong with arg + r = Ractor.new 'ok' do |msg| + msg + end + r.take +} + +# Pass multiple arguments to Ractor.new +assert_equal 'ok', %q{ + # ping-pong with two args + r = Ractor.new 'ping', 'pong' do |msg, msg2| + [msg, msg2] + end + 'ok' if r.take == ['ping', 'pong'] +} + +# Ractor#send passes an object with copy to a Ractor +# and Ractor.receive in the Ractor block can receive the passed value. +assert_equal 'ok', %q{ + r = Ractor.new do + msg = Ractor.receive + end + r.send 'ok' + r.take +} + +# Ractor#receive_if can filter the message +assert_equal '[2, 3, 1]', %q{ + r = Ractor.new Ractor.current do |main| + main << 1 + main << 2 + main << 3 + end + a = [] + a << Ractor.receive_if{|msg| msg == 2} + a << Ractor.receive_if{|msg| msg == 3} + a << Ractor.receive +} + +# Ractor#receive_if with break +assert_equal '[2, [1, :break], 3]', %q{ + r = Ractor.new Ractor.current do |main| + main << 1 + main << 2 + main << 3 + end + + a = [] + a << Ractor.receive_if{|msg| msg == 2} + a << Ractor.receive_if{|msg| break [msg, :break]} + a << Ractor.receive +} + +# Ractor#receive_if can't be called recursively +assert_equal '[[:e1, 1], [:e2, 2]]', %q{ + r = Ractor.new Ractor.current do |main| + main << 1 + main << 2 + main << 3 + end + + a = [] + + Ractor.receive_if do |msg| + begin + Ractor.receive + rescue Ractor::Error + a << [:e1, msg] + end + true # delete 1 from queue + end + + Ractor.receive_if do |msg| + begin + Ractor.receive_if{} + rescue Ractor::Error + a << [:e2, msg] + end + true # delete 2 from queue + end + + a # +} + +# dtoa race condition +assert_equal '[:ok, :ok, :ok]', %q{ + n = 3 + n.times.map{ + Ractor.new{ + 10_000.times{ rand.to_s } + :ok + } + }.map(&:take) +} + +# Ractor.make_shareable issue for locals in proc [Bug #18023] +assert_equal '[:a, :b, :c, :d, :e]', %q{ + v1, v2, v3, v4, v5 = :a, :b, :c, :d, :e + closure = Ractor.current.instance_eval{ Proc.new { [v1, v2, v3, v4, v5] } } + + Ractor.make_shareable(closure).call +} + +# Ractor.make_shareable issue for locals in proc [Bug #18023] +assert_equal '[:a, :b, :c, :d, :e, :f, :g]', %q{ + a = :a + closure = Ractor.current.instance_eval do + -> { + b, c, d = :b, :c, :d + -> { + e, f, g = :e, :f, :g + -> { [a, b, c, d, e, f, g] } + }.call + }.call + end + + Ractor.make_shareable(closure).call +} + +# Now autoload in non-main Ractor is not supported +assert_equal 'ok', %q{ + autoload :Foo, 'foo.rb' + r = Ractor.new do + p Foo + rescue Ractor::UnsafeError + :ok + end + r.take +} + +### +### +# Ractor still has several memory corruption so skip huge number of tests +if ENV['GITHUB_WORKFLOW'] && + ENV['GITHUB_WORKFLOW'] == 'Compilations' + # ignore the follow +else + +# Ractor.select(*ractors) receives a values from a ractors. +# It is similar to select(2) and Go's select syntax. +# The return value is [ch, received_value] +assert_equal 'ok', %q{ + # select 1 + r1 = Ractor.new{'r1'} + r, obj = Ractor.select(r1) + 'ok' if r == r1 and obj == 'r1' +} + +# Ractor.select from two ractors. +assert_equal '["r1", "r2"]', %q{ + # select 2 + r1 = Ractor.new{'r1'} + r2 = Ractor.new{'r2'} + rs = [r1, r2] + as = [] + r, obj = Ractor.select(*rs) + rs.delete(r) + as << obj + r, obj = Ractor.select(*rs) + as << obj + as.sort #=> ["r1", "r2"] +} + +# Ractor.select from multiple ractors. +assert_equal 30.times.map { 'ok' }.to_s, %q{ + def test n + rs = (1..n).map do |i| + Ractor.new(i) do |i| + "r#{i}" + end + end + as = [] + all_rs = rs.dup + + n.times{ + r, obj = Ractor.select(*rs) + as << [r, obj] + rs.delete(r) + } + + if as.map{|r, o| r.object_id}.sort == all_rs.map{|r| r.object_id}.sort && + as.map{|r, o| o}.sort == (1..n).map{|i| "r#{i}"}.sort + 'ok' + else + 'ng' + end + end + + 30.times.map{|i| + test i + } +} unless ENV['RUN_OPTS'] =~ /--mjit-call-threshold=5/ || # This always fails with --mjit-wait --mjit-call-threshold=5 + (ENV.key?('TRAVIS') && ENV['TRAVIS_CPU_ARCH'] == 'arm64') || # https://bugs.ruby-lang.org/issues/17878 + true # too flaky everywhere http://ci.rvm.jp/results/trunk@ruby-sp1/4321096 + +# Exception for empty select +assert_match /specify at least one ractor/, %q{ + begin + Ractor.select + rescue ArgumentError => e + e.message + end +} + +# Outgoing port of a ractor will be closed when the Ractor is terminated. +assert_equal 'ok', %q{ + r = Ractor.new do + 'finish' + end + + r.take + sleep 0.1 until r.inspect =~ /terminated/ + + begin + o = r.take + rescue Ractor::ClosedError + 'ok' + else + "ng: #{o}" + end +} + +# Raise Ractor::ClosedError when try to send into a terminated ractor +assert_equal 'ok', %q{ + r = Ractor.new do + end + + r.take # closed + sleep 0.1 until r.inspect =~ /terminated/ + + begin + r.send(1) + rescue Ractor::ClosedError + 'ok' + else + 'ng' + end +} + +# Raise Ractor::ClosedError when try to send into a closed actor +assert_equal 'ok', %q{ + r = Ractor.new { Ractor.receive } + r.close_incoming + + begin + r.send(1) + rescue Ractor::ClosedError + 'ok' + else + 'ng' + end +} + +# Raise Ractor::ClosedError when try to take from closed actor +assert_equal 'ok', %q{ + r = Ractor.new do + Ractor.yield 1 + Ractor.receive + end + + r.close_outgoing + begin + r.take + rescue Ractor::ClosedError + 'ok' + else + 'ng' + end +} + +# Can mix with Thread#interrupt and Ractor#take [Bug #17366] +assert_equal 'err', %q{ + Ractor.new{ + t = Thread.current + begin + Thread.new{ t.raise "err" }.join + rescue => e + e.message + end + }.take +} + +# Killed Ractor's thread yields nil +assert_equal 'nil', %q{ + Ractor.new{ + t = Thread.current + Thread.new{ t.kill }.join + }.take.inspect #=> nil +} + +# Ractor.yield raises Ractor::ClosedError when outgoing port is closed. +assert_equal 'ok', %q{ + r = Ractor.new Ractor.current do |main| + Ractor.receive + main << true + Ractor.yield 1 + end + + r.close_outgoing + r << true + Ractor.receive + + begin + r.take + rescue Ractor::ClosedError + 'ok' + else + 'ng' + end +} + +# Raise Ractor::ClosedError when try to send into a ractor with closed incoming port +assert_equal 'ok', %q{ + r = Ractor.new { Ractor.receive } + r.close_incoming + + begin + r.send(1) + rescue Ractor::ClosedError + 'ok' + else + 'ng' + end +} + +# A ractor with closed incoming port still can send messages out +assert_equal '[1, 2]', %q{ + r = Ractor.new do + Ractor.yield 1 + 2 + end + r.close_incoming + + [r.take, r.take] +} + +# Raise Ractor::ClosedError when try to take from a ractor with closed outgoing port +assert_equal 'ok', %q{ + r = Ractor.new do + Ractor.yield 1 + Ractor.receive + end + + sleep 0.01 # wait for Ractor.yield in r + r.close_outgoing + begin + r.take + rescue Ractor::ClosedError + 'ok' + else + 'ng' + end +} + +# A ractor with closed outgoing port still can receive messages from incoming port +assert_equal 'ok', %q{ + r = Ractor.new do + Ractor.receive + end + + r.close_outgoing + begin + r.send(1) + rescue Ractor::ClosedError + 'ng' + else + 'ok' + end +} + +# Ractor.main returns main ractor +assert_equal 'true', %q{ + Ractor.new{ + Ractor.main + }.take == Ractor.current +} + +# a ractor with closed outgoing port should terminate +assert_equal 'ok', %q{ + Ractor.new do + close_outgoing + end + + true until Ractor.count == 1 + :ok +} + +# multiple Ractors can receive (wait) from one Ractor +assert_equal '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]', %q{ + pipe = Ractor.new do + loop do + Ractor.yield Ractor.receive + end + end + + RN = 10 + rs = RN.times.map{|i| + Ractor.new pipe, i do |pipe, i| + msg = pipe.take + msg # ping-pong + end + } + RN.times{|i| + pipe << i + } + RN.times.map{ + r, n = Ractor.select(*rs) + rs.delete r + n + }.sort +} unless /mswin/ =~ RUBY_PLATFORM # randomly hangs on mswin https://github.com/ruby/ruby/actions/runs/3753871445/jobs/6377551069#step:20:131 + +# Ractor.select also support multiple take, receive and yield +assert_equal '[true, true, true]', %q{ + RN = 10 + CR = Ractor.current + + rs = (1..RN).map{ + Ractor.new do + CR.send 'send' + CR.take #=> 'sendyield' + 'take' + end + } + received = [] + take = [] + yielded = [] + until rs.empty? + r, v = Ractor.select(CR, *rs, yield_value: 'yield') + case r + when :receive + received << v + when :yield + yielded << v + else + take << v + rs.delete r + end + end + [received.all?('sendyield'), yielded.all?(nil), take.all?('take')] +} + +# multiple Ractors can send to one Ractor +assert_equal '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]', %q{ + pipe = Ractor.new do + loop do + Ractor.yield Ractor.receive + end + end + + RN = 10 + RN.times.map{|i| + Ractor.new pipe, i do |pipe, i| + pipe << i + end + } + RN.times.map{ + pipe.take + }.sort +} + +# an exception in a Ractor will be re-raised at Ractor#receive +assert_equal '[RuntimeError, "ok", true]', %q{ + r = Ractor.new do + raise 'ok' # exception will be transferred receiver + end + begin + r.take + rescue Ractor::RemoteError => e + [e.cause.class, #=> RuntimeError + e.cause.message, #=> 'ok' + e.ractor == r] #=> true + end +} + +# threads in a ractor will killed +assert_equal '{:ok=>3}', %q{ + Ractor.new Ractor.current do |main| + q = Thread::Queue.new + Thread.new do + q << true + loop{} + ensure + main << :ok + end + + Thread.new do + q << true + while true + end + ensure + main << :ok + end + + Thread.new do + q << true + sleep 1 + ensure + main << :ok + end + + # wait for the start of all threads + 3.times{q.pop} + end + + 3.times.map{Ractor.receive}.tally +} + +# unshareable object are copied +assert_equal 'false', %q{ + obj = 'str'.dup + r = Ractor.new obj do |msg| + msg.object_id + end + + obj.object_id == r.take +} + +# To copy the object, now Marshal#dump is used +assert_equal "allocator undefined for Thread", %q{ + obj = Thread.new{} + begin + r = Ractor.new obj do |msg| + msg + end + rescue TypeError => e + e.message #=> no _dump_data is defined for class Thread + else + 'ng' + end +} + +# send shareable and unshareable objects +assert_equal "ok", %q{ + echo_ractor = Ractor.new do + loop do + v = Ractor.receive + Ractor.yield v + end + end + + class C; end + module M; end + S = Struct.new(:a, :b, :c, :d) + + shareable_objects = [ + true, + false, + nil, + 1, + 1.1, # Float + 1+2r, # Rational + 3+4i, # Complex + 2**128, # Bignum + :sym, # Symbol + 'xyzzy'.to_sym, # dynamic symbol + 'frozen'.freeze, # frozen String + /regexp/, # regexp literal + /reg{true}exp/.freeze, # frozen dregexp + [1, 2].freeze, # frozen Array which only refers to shareable + {a: 1}.freeze, # frozen Hash which only refers to shareable + [{a: 1}.freeze, 'str'.freeze].freeze, # nested frozen container + S.new(1, 2).freeze, # frozen Struct + S.new(1, 2, 3, 4).freeze, # frozen Struct + (1..2), # Range on Struct + (1..), # Range on Struct + (..1), # Range on Struct + C, # class + M, # module + Ractor.current, # Ractor + ] + + unshareable_objects = [ + 'mutable str'.dup, + [:array], + {hash: true}, + S.new(1, 2), + S.new(1, 2, 3, 4), + S.new("a", 2).freeze, # frozen, but refers to an unshareable object + ] + + results = [] + + shareable_objects.map{|o| + echo_ractor << o + o2 = echo_ractor.take + results << "#{o} is copied" unless o.object_id == o2.object_id + } + + unshareable_objects.map{|o| + echo_ractor << o + o2 = echo_ractor.take + results << "#{o.inspect} is not copied" if o.object_id == o2.object_id + } + + if results.empty? + :ok + else + results.inspect + end +} + +# frozen Objects are shareable +assert_equal [false, true, false].inspect, %q{ + class C + def initialize freeze + @a = 1 + @b = :sym + @c = 'frozen_str' + @c.freeze if freeze + @d = true + end + end + + def check obj1 + obj2 = Ractor.new obj1 do |obj| + obj + end.take + + obj1.object_id == obj2.object_id + end + + results = [] + results << check(C.new(true)) # false + results << check(C.new(true).freeze) # true + results << check(C.new(false).freeze) # false +} + +# move example2: String +# touching moved object causes an error +assert_equal 'hello world', %q{ + # move + r = Ractor.new do + obj = Ractor.receive + obj << ' world' + end + + str = 'hello' + r.send str, move: true + modified = r.take + + begin + str << ' exception' # raise Ractor::MovedError + rescue Ractor::MovedError + modified #=> 'hello world' + else + raise 'unreachable' + end +} + +# move example2: Array +assert_equal '[0, 1]', %q{ + r = Ractor.new do + ary = Ractor.receive + ary << 1 + end + + a1 = [0] + r.send a1, move: true + a2 = r.take + begin + a1 << 2 # raise Ractor::MovedError + rescue Ractor::MovedError + a2.inspect + end +} + +# move with yield +assert_equal 'hello', %q{ + r = Ractor.new do + Thread.current.report_on_exception = false + obj = 'hello' + Ractor.yield obj, move: true + obj << 'world' + end + + str = r.take + begin + r.take + rescue Ractor::RemoteError + str #=> "hello" + end +} + +# yield/move should not make moved object when the yield is not succeeded +assert_equal '"str"', %q{ + R = Ractor.new{} + M = Ractor.current + r = Ractor.new do + s = 'str' + selected_r, v = Ractor.select R, yield_value: s, move: true + raise if selected_r != R # taken from R + M.send s.inspect # s should not be a moved object + end + + Ractor.receive +} + +# yield/move can fail +assert_equal "allocator undefined for Thread", %q{ + r = Ractor.new do + obj = Thread.new{} + Ractor.yield obj + rescue => e + e.message + end + r.take +} + +# Access to global-variables are prohibited +assert_equal 'can not access global variables $gv from non-main Ractors', %q{ + $gv = 1 + r = Ractor.new do + $gv + end + + begin + r.take + rescue Ractor::RemoteError => e + e.cause.message + end +} + +# Access to global-variables are prohibited +assert_equal 'can not access global variables $gv from non-main Ractors', %q{ + r = Ractor.new do + $gv = 1 + end + + begin + r.take + rescue Ractor::RemoteError => e + e.cause.message + end +} + +# $stdin,out,err is Ractor local, but shared fds +assert_equal 'ok', %q{ + r = Ractor.new do + [$stdin, $stdout, $stderr].map{|io| + [io.object_id, io.fileno] + } + end + + [$stdin, $stdout, $stderr].zip(r.take){|io, (oid, fno)| + raise "should not be different object" if io.object_id == oid + raise "fd should be same" unless io.fileno == fno + } + 'ok' +} + +# $stdin,out,err belong to Ractor +assert_equal 'ok', %q{ + r = Ractor.new do + $stdin.itself + $stdout.itself + $stderr.itself + 'ok' + end + + r.take +} + +# $DEBUG, $VERBOSE are Ractor local +assert_equal 'true', %q{ + $DEBUG = true + $VERBOSE = true + + def ractor_local_globals + /a(b)(c)d/ =~ 'abcd' # for $~ + `echo foo` unless /solaris/ =~ RUBY_PLATFORM + + { + # ractor-local (derived from created ractor): debug + '$DEBUG' => $DEBUG, + '$-d' => $-d, + + # ractor-local (derived from created ractor): verbose + '$VERBOSE' => $VERBOSE, + '$-w' => $-w, + '$-W' => $-W, + '$-v' => $-v, + + # process-local (readonly): other commandline parameters + '$-p' => $-p, + '$-l' => $-l, + '$-a' => $-a, + + # process-local (readonly): getpid + '$$' => $$, + + # thread local: process result + '$?' => $?, + + # scope local: match + '$~' => $~.inspect, + '$&' => $&, + '$`' => $`, + '$\'' => $', + '$+' => $+, + '$1' => $1, + + # scope local: last line + '$_' => $_, + + # scope local: last backtrace + '$@' => $@, + '$!' => $!, + + # ractor local: stdin, out, err + '$stdin' => $stdin.inspect, + '$stdout' => $stdout.inspect, + '$stderr' => $stderr.inspect, + } + end + + h = Ractor.new do + ractor_local_globals + end.take + ractor_local_globals == h #=> true +} + +# selfs are different objects +assert_equal 'false', %q{ + r = Ractor.new do + self.object_id + end + r.take == self.object_id #=> false +} + +# self is a Ractor instance +assert_equal 'true', %q{ + r = Ractor.new do + self.object_id + end + r.object_id == r.take #=> true +} + +# given block Proc will be isolated, so can not access outer variables. +assert_equal 'ArgumentError', %q{ + begin + a = true + r = Ractor.new do + a + end + rescue => e + e.class + end +} + +# ivar in shareable-objects are not allowed to access from non-main Ractor +assert_equal "can not get unshareable values from instance variables of classes/modules from non-main Ractors", %q{ + class C + @iv = 'str' + end + + r = Ractor.new do + class C + p @iv + end + end + + + begin + r.take + rescue Ractor::RemoteError => e + e.cause.message + end +} + +# ivar in shareable-objects are not allowed to access from non-main Ractor +assert_equal 'can not access instance variables of shareable objects from non-main Ractors', %q{ + shared = Ractor.new{} + shared.instance_variable_set(:@iv, 'str') + + r = Ractor.new shared do |shared| + p shared.instance_variable_get(:@iv) + end + + begin + r.take + rescue Ractor::RemoteError => e + e.cause.message + end +} + +# ivar in shareable-objects are not allowed to access from non-main Ractor, by @iv (get) +assert_equal 'can not access instance variables of shareable objects from non-main Ractors', %q{ + class Ractor + def setup + @foo = '' + end + + def foo + @foo + end + end + + shared = Ractor.new{} + shared.setup + + r = Ractor.new shared do |shared| + p shared.foo + end + + begin + r.take + rescue Ractor::RemoteError => e + e.cause.message + end +} + +# ivar in shareable-objects are not allowed to access from non-main Ractor, by @iv (set) +assert_equal 'can not access instance variables of shareable objects from non-main Ractors', %q{ + class Ractor + def setup + @foo = '' + end + end + + shared = Ractor.new{} + + r = Ractor.new shared do |shared| + p shared.setup + end + + begin + r.take + rescue Ractor::RemoteError => e + e.cause.message + end +} + +# But a shareable object is frozen, it is allowed to access ivars from non-main Ractor +assert_equal '11', %q{ + [Object.new, [], ].map{|obj| + obj.instance_variable_set('@a', 1) + Ractor.make_shareable obj = obj.freeze + + Ractor.new obj do |obj| + obj.instance_variable_get('@a') + end.take.to_s + }.join +} + +# and instance variables of classes/modules are accessible if they refer shareable objects +assert_equal '333', %q{ + class C + @int = 1 + @str = '-1000'.dup + @fstr = '100'.freeze + + def self.int = @int + def self.str = @str + def self.fstr = @fstr + end + + module M + @int = 2 + @str = '-2000'.dup + @fstr = '200'.freeze + + def self.int = @int + def self.str = @str + def self.fstr = @fstr + end + + a = Ractor.new{ C.int }.take + b = Ractor.new do + C.str.to_i + rescue Ractor::IsolationError + 10 + end.take + c = Ractor.new do + C.fstr.to_i + end.take + + d = Ractor.new{ M.int }.take + e = Ractor.new do + M.str.to_i + rescue Ractor::IsolationError + 20 + end.take + f = Ractor.new do + M.fstr.to_i + end.take + + + # 1 + 10 + 100 + 2 + 20 + 200 + a + b + c + d + e + f +} + +# cvar in shareable-objects are not allowed to access from non-main Ractor +assert_equal 'can not access class variables from non-main Ractors', %q{ + class C + @@cv = 'str' + end + + r = Ractor.new do + class C + p @@cv + end + end + + begin + r.take + rescue Ractor::RemoteError => e + e.cause.message + end +} + +# also cached cvar in shareable-objects are not allowed to access from non-main Ractor +assert_equal 'can not access class variables from non-main Ractors', %q{ + class C + @@cv = 'str' + def self.cv + @@cv + end + end + + C.cv # cache + + r = Ractor.new do + C.cv + end + + begin + r.take + rescue Ractor::RemoteError => e + e.cause.message + end +} + +# Getting non-shareable objects via constants by other Ractors is not allowed +assert_equal 'can not access non-shareable objects in constant C::CONST by non-main Ractor.', %q{ + class C + CONST = 'str' + end + r = Ractor.new do + C::CONST + end + begin + r.take + rescue Ractor::RemoteError => e + e.cause.message + end +} + +# Constant cache should care about non-sharable constants +assert_equal "can not access non-shareable objects in constant Object::STR by non-main Ractor.", %q{ + STR = "hello" + def str; STR; end + s = str() # fill const cache + begin + Ractor.new{ str() }.take + rescue Ractor::RemoteError => e + e.cause.message + end +} + +# Setting non-shareable objects into constants by other Ractors is not allowed +assert_equal 'can not set constants with non-shareable objects by non-main Ractors', %q{ + class C + end + r = Ractor.new do + C::CONST = 'str' + end + begin + r.take + rescue Ractor::RemoteError => e + e.cause.message + end +} + +# define_method is not allowed +assert_equal "defined with an un-shareable Proc in a different Ractor", %q{ + str = "foo" + define_method(:buggy){|i| str << "#{i}"} + begin + Ractor.new{buggy(10)}.take + rescue => e + e.cause.message + end +} + +# Immutable Array and Hash are shareable, so it can be shared with constants +assert_equal '[1000, 3]', %q{ + A = Array.new(1000).freeze # [nil, ...] + H = {a: 1, b: 2, c: 3}.freeze + + Ractor.new{ [A.size, H.size] }.take +} + +# Ractor.count +assert_equal '[1, 4, 3, 2, 1]', %q{ + counts = [] + counts << Ractor.count + ractors = (1..3).map { Ractor.new { Ractor.receive } } + counts << Ractor.count + + ractors[0].send('End 0').take + sleep 0.1 until ractors[0].inspect =~ /terminated/ + counts << Ractor.count + + ractors[1].send('End 1').take + sleep 0.1 until ractors[1].inspect =~ /terminated/ + counts << Ractor.count + + ractors[2].send('End 2').take + sleep 0.1 until ractors[2].inspect =~ /terminated/ + counts << Ractor.count + + counts.inspect +} + +# ObjectSpace.each_object can not handle unshareable objects with Ractors +assert_equal '0', %q{ + Ractor.new{ + n = 0 + ObjectSpace.each_object{|o| n += 1 unless Ractor.shareable?(o)} + n + }.take +} + +# ObjectSpace._id2ref can not handle unshareable objects with Ractors +assert_equal 'ok', %q{ + s = 'hello' + + Ractor.new s.object_id do |id ;s| + begin + s = ObjectSpace._id2ref(id) + rescue => e + :ok + end + end.take +} + +# Ractor.make_shareable(obj) +assert_equal 'true', %q{ + class C + def initialize + @a = 'foo' + @b = 'bar' + end + + def freeze + @c = [:freeze_called] + super + end + + attr_reader :a, :b, :c + end + S = Struct.new(:s1, :s2) + str = "hello" + str.instance_variable_set("@iv", "hello") + /a/ =~ 'a' + m = $~ + class N < Numeric + def /(other) + 1 + end + end + ary = []; ary << ary + + a = [[1, ['2', '3']], + {Object.new => "hello"}, + C.new, + S.new("x", "y"), + ("a".."b"), + str, + ary, # cycle + /regexp/, + /#{'r'.upcase}/, + m, + Complex(N.new,0), + Rational(N.new,0), + true, + false, + nil, + 1, 1.2, 1+3r, 1+4i, # Numeric + ] + Ractor.make_shareable(a) + + # check all frozen + a.each{|o| + raise o.inspect unless o.frozen? + + case o + when C + raise o.a.inspect unless o.a.frozen? + raise o.b.inspect unless o.b.frozen? + raise o.c.inspect unless o.c.frozen? && o.c == [:freeze_called] + when Rational + raise o.numerator.inspect unless o.numerator.frozen? + when Complex + raise o.real.inspect unless o.real.frozen? + when Array + if o[0] == 1 + raise o[1][1].inspect unless o[1][1].frozen? + end + when Hash + o.each{|k, v| + raise k.inspect unless k.frozen? + raise v.inspect unless v.frozen? + } + end + } + + Ractor.shareable?(a) +} + +# Ractor.make_shareable(obj) doesn't freeze shareable objects +assert_equal 'true', %q{ + r = Ractor.new{} + Ractor.make_shareable(a = [r]) + [a.frozen?, a[0].frozen?] == [true, false] +} + +# Ractor.make_shareable(a_proc) makes a proc shareable. +assert_equal 'true', %q{ + a = [1, [2, 3], {a: "4"}] + + pr = Ractor.current.instance_eval do + Proc.new do + a + end + end + + Ractor.make_shareable(a) # referred value should be shareable + Ractor.make_shareable(pr) + Ractor.shareable?(pr) +} + +# Ractor.shareable?(recursive_objects) +assert_equal '[false, false]', %q{ + y = [] + x = [y, {}].freeze + y << x + y.freeze + [Ractor.shareable?(x), Ractor.shareable?(y)] +} + +# Ractor.make_shareable(recursive_objects) +assert_equal '[:ok, false, false]', %q{ + o = Object.new + def o.freeze; raise; end + y = [] + x = [y, o].freeze + y << x + y.freeze + [(Ractor.make_shareable(x) rescue :ok), Ractor.shareable?(x), Ractor.shareable?(y)] +} + +# Ractor.make_shareable with Class/Module +assert_equal '[C, M]', %q{ + class C; end + module M; end + + Ractor.make_shareable(ary = [C, M]) +} + +# define_method() can invoke different Ractor's proc if the proc is shareable. +assert_equal '1', %q{ + class C + a = 1 + define_method "foo", Ractor.make_shareable(Proc.new{ a }) + a = 2 + end + + Ractor.new{ C.new.foo }.take +} + +# Ractor.make_shareable(a_proc) makes a proc shareable. +assert_equal 'can not make a Proc shareable because it accesses outer variables (a).', %q{ + a = b = nil + pr = Ractor.current.instance_eval do + Proc.new do + c = b # assign to a is okay because c is block local variable + # reading b is okay + a = b # assign to a is not allowed #=> Ractor::Error + end + end + + begin + Ractor.make_shareable(pr) + rescue => e + e.message + end +} + +# Ractor.make_shareable(obj, copy: true) makes copied shareable object. +assert_equal '[false, false, true, true]', %q{ + r = [] + o1 = [1, 2, ["3"]] + + o2 = Ractor.make_shareable(o1, copy: true) + r << Ractor.shareable?(o1) # false + r << (o1.object_id == o2.object_id) # false + + o3 = Ractor.make_shareable(o1) + r << Ractor.shareable?(o1) # true + r << (o1.object_id == o3.object_id) # false + r +} + +# TracePoint with normal Proc should be Ractor local +assert_equal '[4, 8]', %q{ + rs = [] + TracePoint.new(:line){|tp| rs << tp.lineno if tp.path == __FILE__}.enable do + Ractor.new{ # line 4 + a = 1 + b = 2 + }.take + c = 3 # line 8 + end + rs +} + +# Ractor deep copies frozen objects (ary) +assert_equal '[true, false]', %q{ + Ractor.new([[]].freeze) { |ary| + [ary.frozen?, ary.first.frozen? ] + }.take +} + +# Ractor deep copies frozen objects (str) +assert_equal '[true, false]', %q{ + s = String.new.instance_eval { @x = []; freeze} + Ractor.new(s) { |s| + [s.frozen?, s.instance_variable_get(:@x).frozen?] + }.take +} + +# Can not trap with not isolated Proc on non-main ractor +assert_equal '[:ok, :ok]', %q{ + a = [] + Ractor.new{ + trap(:INT){p :ok} + }.take + a << :ok + + begin + Ractor.new{ + s = 'str' + trap(:INT){p s} + }.take + rescue => Ractor::RemoteError + a << :ok + end +} + +# Ractor-local storage +assert_equal '[nil, "b", "a"]', %q{ + ans = [] + Ractor.current[:key] = 'a' + r = Ractor.new{ + Ractor.yield self[:key] + self[:key] = 'b' + self[:key] + } + ans << r.take + ans << r.take + ans << Ractor.current[:key] +} + +### +### Synchronization tests +### + +N = 100_000 + +# fstring pool +assert_equal "#{N}#{N}", %Q{ + N = #{N} + 2.times.map{ + Ractor.new{ + N.times{|i| -(i.to_s)} + } + }.map{|r| r.take}.join +} + +# enc_table +assert_equal "100", %Q{ + Ractor.new do + loop do + Encoding.find("test-enc-#{rand(5_000)}").inspect + rescue ArgumentError => e + end + end + + src = Encoding.find("UTF-8") + 100.times{|i| + src.replicate("test-enc-\#{i}") + } +} + +# Generic ivtbl +n = N/2 +assert_equal "#{n}#{n}", %Q{ + 2.times.map{ + Ractor.new do + #{n}.times do + obj = '' + obj.instance_variable_set("@a", 1) + obj.instance_variable_set("@b", 1) + obj.instance_variable_set("@c", 1) + obj.instance_variable_defined?("@a") + end + end + }.map{|r| r.take}.join +} + +# NameError +assert_equal "ok", %q{ + begin + bar + rescue => err + end + begin + Ractor.new{} << err + rescue TypeError + 'ok' + end +} + +assert_equal "ok", %q{ + GC.disable + Ractor.new {} + raise "not ok" unless GC.disable + + foo = [] + 10.times { foo << 1 } + + GC.start + + 'ok' +} + +# Can yield back values while GC is sweeping [Bug #18117] +assert_equal "ok", %q{ + workers = (0...8).map do + Ractor.new do + loop do + 10_000.times.map { Object.new } + Ractor.yield Time.now + end + end + end + + 1_000.times { idle_worker, tmp_reporter = Ractor.select(*workers) } + "ok" +} + +assert_equal "ok", %q{ + def foo(*); ->{ super }; end + begin + Ractor.make_shareable(foo) + rescue Ractor::IsolationError + "ok" + end +} + +assert_equal "ok", %q{ + def foo(**); ->{ super }; end + begin + Ractor.make_shareable(foo) + rescue Ractor::IsolationError + "ok" + end +} + +assert_equal "ok", %q{ + def foo(...); ->{ super }; end + begin + Ractor.make_shareable(foo) + rescue Ractor::IsolationError + "ok" + end +} + +assert_equal "ok", %q{ + def foo((x), (y)); ->{ super }; end + begin + Ractor.make_shareable(foo([], [])) + rescue Ractor::IsolationError + "ok" + end +} + +assert_equal "ok", %q{ + module M + def foo + @foo + end + end + + class A + include M + + def initialize + 100.times { |i| instance_variable_set(:"@var_#{i}", "bad: #{i}") } + @foo = 2 + end + end + + class B + include M + + def initialize + @foo = 1 + end + end + + Ractor.new do + b = B.new + 100_000.times do + raise unless b.foo == 1 + end + end + + a = A.new + 100_000.times do + raise unless a.foo == 2 + end + + "ok" +} + +assert_match /\Atest_ractor\.rb:1:\s+warning:\s+Ractor is experimental/, %q{ + Warning[:experimental] = $VERBOSE = true + STDERR.reopen(STDOUT) + eval("Ractor.new{}.take", nil, "test_ractor.rb", 1) +} + +end # if !ENV['GITHUB_WORKFLOW'] diff --git a/bootstraptest/test_syntax.rb b/bootstraptest/test_syntax.rb index a111990a1f..948e2d7809 100644 --- a/bootstraptest/test_syntax.rb +++ b/bootstraptest/test_syntax.rb @@ -268,8 +268,10 @@ assert_equal %q{}, %q{ defined?(@@a) } assert_equal %q{class variable}, %q{ - @@a = 1 - defined?(@@a) + class A + @@a = 1 + defined?(@@a) + end } assert_equal %q{}, %q{ defined?($a) @@ -626,7 +628,7 @@ assert_equal '2', %q{ } assert_match /invalid multibyte char/, %q{ - STDERR.reopen(STDOUT) + $stderr = STDOUT eval("\"\xf0".force_encoding("utf-8")) }, '[ruby-dev:32429]' diff --git a/bootstraptest/test_thread.rb b/bootstraptest/test_thread.rb index d16295de8b..5361828403 100644 --- a/bootstraptest/test_thread.rb +++ b/bootstraptest/test_thread.rb @@ -1,4 +1,14 @@ -# Thread and Fiber +show_limit %q{ + threads = [] + begin + threads << Thread.new{sleep} + + raise Exception, "skipping" if threads.count >= 10_000 + rescue Exception => error + puts "Thread count: #{threads.count} (#{error})" + break + end while true +} if false # disable to pass CI assert_equal %q{ok}, %q{ Thread.new{ @@ -36,7 +46,7 @@ begin } } rescue ThreadError => e - :ok if /can't create Thread/ =~ e.message + /can't create Thread/ =~ e.message ? :ok : e.message end } assert_equal %q{ok}, %q{ @@ -50,7 +60,7 @@ begin } } rescue ThreadError => e - :ok if /can't create Thread/ =~ e.message + /can't create Thread/ =~ e.message ? :ok : e.message end } assert_equal %q{ok}, %q{ @@ -233,7 +243,7 @@ assert_equal 'true', %{ } assert_equal 'ok', %{ - open("zzz.rb", "w") do |f| + open("zzz_t1.rb", "w") do |f| f.puts <<-END begin Thread.new { fork { GC.start } }.join @@ -244,7 +254,7 @@ assert_equal 'ok', %{ end END end - require "./zzz.rb" + require "./zzz_t1.rb" $result } @@ -299,10 +309,6 @@ assert_equal 'ok', %q{ }, '[ruby-dev:34492]' assert_normal_exit %q{ - at_exit { Fiber.new{}.resume } -} - -assert_normal_exit %q{ g = enum_for(:local_variables) loop { g.next } }, '[ruby-dev:34128]' @@ -328,10 +334,6 @@ assert_normal_exit %q{ }, '[ruby-dev:34128]' assert_normal_exit %q{ - Fiber.new(&Object.method(:class_eval)).resume("foo") -}, '[ruby-dev:34128]' - -assert_normal_exit %q{ Thread.new("foo", &Object.method(:class_eval)).join }, '[ruby-dev:34128]' @@ -406,7 +408,7 @@ assert_equal 'ok', %q{ } assert_equal 'ok', %{ - open("zzz.rb", "w") do |f| + open("zzz_t2.rb", "w") do |f| f.puts <<-'end;' # do begin m = Thread::Mutex.new @@ -430,7 +432,7 @@ assert_equal 'ok', %{ end end; end - require "./zzz.rb" + require "./zzz_t2.rb" $result } @@ -482,3 +484,17 @@ assert_equal 'foo', %q{ GC.start f.call.source } +assert_normal_exit %q{ + class C + def inspect + sleep 0.5 + 'C!!' + end + end + Thread.new{ + loop{ + p C.new + } + } + sleep 0.1 +}, timeout: 5 diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb new file mode 100644 index 0000000000..5c655b8f25 --- /dev/null +++ b/bootstraptest/test_yjit.rb @@ -0,0 +1,3530 @@ +# Regression test for yielding with autosplat to block with +# optional parameters. https://github.com/Shopify/yjit/issues/313 +assert_equal '[:a, :b, :a, :b]', %q{ + def yielder(arg) = yield(arg) + yield(arg) + + yielder([:a, :b]) do |c = :c, d = :d| + [c, d] + end +} + +# Regression test for GC mishap while doing shape transition +assert_equal '[:ok]', %q{ + # [Bug #19601] + class RegressionTest + def initialize + @a = @b = @fourth_ivar_does_shape_transition = nil + end + + def extender + @first_extended_ivar = [:ok] + end + end + + GC.stress = true + + # Used to crash due to GC run in rb_ensure_iv_list_size() + # not marking the newly allocated [:ok]. + RegressionTest.new.extender.itself +} + +assert_equal 'true', %q{ + # regression test for tracking type of locals for too long + def local_setting_cmp(five) + victim = 5 + five.define_singleton_method(:respond_to?) do |_, _| + victim = nil + end + + # +1 makes YJIT track that victim is a number and + # defined? calls respond_to? from above indirectly + unless (victim + 1) && defined?(five.something) + # Would return wrong result if we still think `five` is a number + victim.nil? + end + end + + local_setting_cmp(Object.new) + local_setting_cmp(Object.new) +} + +assert_equal '18374962167983112447', %q{ + # regression test for incorrectly discarding 32 bits of a pointer when it + # comes to default values. + def large_literal_default(n: 0xff00_fabcafe0_00ff) + n + end + + def call_graph_root + large_literal_default + end + + call_graph_root + call_graph_root +} + +assert_normal_exit %q{ + # regression test for a leak caught by an assert on --yjit-call-threshold=2 + Foo = 1 + + eval("def foo = [#{(['Foo,']*256).join}]") + + foo + foo + + Object.send(:remove_const, :Foo) +} + +assert_normal_exit %q{ + # Test to ensure send on overriden c functions + # doesn't corrupt the stack + class Bar + def bar(x) + x + end + end + + class Foo + def bar + Bar.new + end + end + + foo = Foo.new + # before this change, this line would error + # because "s" would still be on the stack + # String.to_s is the overridden method here + p foo.bar.bar("s".__send__(:to_s)) +} + + +assert_equal '[nil, nil, nil, nil, nil, nil]', %q{ + [NilClass, TrueClass, FalseClass, Integer, Float, Symbol].each do |klass| + klass.class_eval("def foo = @foo") + end + + [nil, true, false, 0xFABCAFE, 0.42, :cake].map do |instance| + instance.foo + instance.foo + end +} + +assert_equal '0', %q{ + # This is a regression test for incomplete invalidation from + # opt_setinlinecache. This test might be brittle, so + # feel free to remove it in the future if it's too annoying. + # This test assumes --yjit-call-threshold=2. + module M + Foo = 1 + def foo + Foo + end + + def pin_self_type_then_foo + _ = @foo + foo + end + + def only_ints + 1 + self + foo + end + end + + class Integer + include M + end + + class Sub + include M + end + + foo_method = M.instance_method(:foo) + + dbg = ->(message) do + return # comment this out to get printouts + + $stderr.puts RubyVM::YJIT.disasm(foo_method) + $stderr.puts message + end + + 2.times { 42.only_ints } + + dbg["There should be two versions of getinlineache"] + + module M + remove_const(:Foo) + end + + dbg["There should be no getinlinecaches"] + + 2.times do + 42.only_ints + rescue NameError => err + _ = "caught name error #{err}" + end + + dbg["There should be one version of getinlineache"] + + 2.times do + Sub.new.pin_self_type_then_foo + rescue NameError + _ = 'second specialization' + end + + dbg["There should be two versions of getinlineache"] + + module M + Foo = 1 + end + + dbg["There should still be two versions of getinlineache"] + + 42.only_ints + + dbg["There should be no getinlinecaches"] + + # Find name of the first VM instruction in M#foo. + insns = RubyVM::InstructionSequence.of(foo_method).to_a + if defined?(RubyVM::YJIT.blocks_for) && (insns.last.find { Array === _1 }&.first == :opt_getinlinecache) + RubyVM::YJIT.blocks_for(RubyVM::InstructionSequence.of(foo_method)) + .filter { _1.iseq_start_index == 0 }.count + else + 0 # skip the test + end +} + +# Check that frozen objects are respected +assert_equal 'great', %q{ + class Foo + attr_accessor :bar + def initialize + @bar = 1 + freeze + end + end + + foo = Foo.new + + 5.times do + begin + foo.bar = 2 + rescue FrozenError + end + end + + foo.bar == 1 ? "great" : "NG" +} + +# Check that global variable set works +assert_equal 'string', %q{ + def foo + $foo = "string" + end + + foo +} + +# Check that exceptions work when setting global variables +assert_equal 'rescued', %q{ + def set_var + $var = 100 + rescue + :rescued + end + + set_var + trace_var(:$var) { raise } + set_var +} + +# Check that global variables work +assert_equal 'string', %q{ + $foo = "string" + + def foo + $foo + end + + foo +} + +# Check that exceptions work when getting global variable +assert_equal 'rescued', %q{ + Warning[:deprecated] = true + + module Warning + def warn(message) + raise + end + end + + def get_var + $= + rescue + :rescued + end + + $VERBOSE = true + get_var + get_var +} + +# Check that global tracepoints work +assert_equal 'true', %q{ + def foo + 1 + end + + foo + foo + foo + + called = false + + tp = TracePoint.new(:return) { |event| + if event.method_id == :foo + called = true + end + } + tp.enable + foo + tp.disable + called +} + +# Check that local tracepoints work +assert_equal 'true', %q{ + def foo + 1 + end + + foo + foo + foo + + called = false + + tp = TracePoint.new(:return) { |_| called = true } + tp.enable(target: method(:foo)) + foo + tp.disable + called +} + +# Make sure that optional param methods return the correct value +assert_equal '1', %q{ + def m(ary = []) + yield(ary) + end + + # Warm the JIT with a 0 param call + 2.times { m { } } + m(1) { |v| v } +} + +# Test for topn +assert_equal 'array', %q{ + def threequals(a) + case a + when Array + "array" + when Hash + "hash" + else + "unknown" + end + end + + threequals([]) + threequals([]) + threequals([]) +} + +# Test for opt_mod +assert_equal '2', %q{ + def mod(a, b) + a % b + end + + mod(7, 5) + mod(7, 5) +} + +# Test for opt_mult +assert_equal '12', %q{ + def mult(a, b) + a * b + end + + mult(6, 2) + mult(6, 2) +} + +# Test for opt_div +assert_equal '3', %q{ + def div(a, b) + a / b + end + + div(6, 2) + div(6, 2) +} + +# BOP redefined methods work when JIT compiled +assert_equal 'false', %q{ + def less_than x + x < 10 + end + + class Integer + def < x + false + end + end + + less_than 2 + less_than 2 + less_than 2 +} + +# BOP redefinition works on Integer#< +assert_equal 'false', %q{ + def less_than x + x < 10 + end + + less_than 2 + less_than 2 + + class Integer + def < x + false + end + end + + less_than 2 +} + +# Putobject, less-than operator, fixnums +assert_equal '2', %q{ + def check_index(index) + if 0x40000000 < index + raise "wat? #{index}" + end + index + end + check_index 2 + check_index 2 +} + +# foo leaves a temp on the stack before the call +assert_equal '6', %q{ + def bar + return 5 + end + + def foo + return 1 + bar + end + + foo() + retval = foo() +} + +# Method with one arguments +# foo leaves a temp on the stack before the call +assert_equal '7', %q{ + def bar(a) + return a + 1 + end + + def foo + return 1 + bar(5) + end + + foo() + retval = foo() +} + +# Method with two arguments +# foo leaves a temp on the stack before the call +assert_equal '0', %q{ + def bar(a, b) + return a - b + end + + def foo + return 1 + bar(1, 2) + end + + foo() + retval = foo() +} + +# Passing argument types to callees +assert_equal '8.5', %q{ + def foo(x, y) + x + y + end + + def bar + foo(7, 1.5) + end + + bar + bar +} + +# Recursive Ruby-to-Ruby calls +assert_equal '21', %q{ + def fib(n) + if n < 2 + return n + end + + return fib(n-1) + fib(n-2) + end + + r = fib(8) +} + +# Ruby-to-Ruby call and C call +assert_normal_exit %q{ + def bar + puts('hi!') + end + + def foo + bar + end + + foo() + foo() +} + +# Method aliasing +assert_equal '42', %q{ + class Foo + def method_a + 42 + end + + alias method_b method_a + + def method_a + :somethingelse + end + end + + @obj = Foo.new + + def test + @obj.method_b + end + + test + test +} + +# Method aliasing with method from parent class +assert_equal '777', %q{ + class A + def method_a + 777 + end + end + + class B < A + alias method_b method_a + end + + @obj = B.new + + def test + @obj.method_b + end + + test + test +} + +# The hash method is a C function and uses the self argument +assert_equal 'true', %q{ + def lehashself + hash + end + + a = lehashself + b = lehashself + a == b +} + +# Method redefinition (code invalidation) test +assert_equal '1', %q{ + def ret1 + return 1 + end + + klass = Class.new do + def alias_then_hash(klass, method_to_redefine) + # Redefine the method to be ret1 + klass.alias_method(method_to_redefine, :ret1) + hash + end + end + + instance = klass.new + + i = 0 + while i < 12 + if i < 11 + # Redefine the bar method + instance.alias_then_hash(klass, :bar) + else + # Redefine the hash method to be ret1 + retval = instance.alias_then_hash(klass, :hash) + end + i += 1 + end + + retval +} + +# Code invalidation and opt_getinlinecache +assert_normal_exit %q{ + class Foo; end + + # Uses the class constant Foo + def use_constant(arg) + [Foo.new, arg] + end + + def propagate_type + i = Array.new + i.itself # make it remember that i is on-heap + use_constant(i) + end + + propagate_type + propagate_type + use_constant(Foo.new) + class Jo; end # bump global constant state + use_constant(3) +} + +# Method redefinition (code invalidation) and GC +assert_equal '7', %q{ + def bar() + return 5 + end + + def foo() + bar() + end + + foo() + foo() + + def bar() + return 7 + end + + 4.times { GC.start } + + foo() + foo() +} + +# Method redefinition with two block versions +assert_equal '7', %q{ + def bar() + return 5 + end + + def foo(n) + return ((n < 5)? 5:false), bar() + end + + foo(4) + foo(4) + foo(10) + foo(10) + + def bar() + return 7 + end + + 4.times { GC.start } + + foo(4) + foo(4)[1] +} + +# Method redefinition while the method is on the stack +assert_equal '[777, 1]', %q{ + def foo + redef() + 777 + end + + def redef + # Redefine the global foo + eval("def foo; 1; end", TOPLEVEL_BINDING) + + # Collect dead code + GC.stress = true + GC.start + + # But we will return to the original foo, + # which remains alive because it's on the stack + end + + # Must produce [777, 1] + [foo, foo] +} + +# Test for GC safety. Don't invalidate dead iseqs. +assert_normal_exit %q{ + Class.new do + def foo + itself + end + + new.foo + new.foo + new.foo + new.foo + end + + 4.times { GC.start } + def itself + self + end +} + +# test setinstancevariable on extended objects +assert_equal '1', %q{ + class Extended + attr_reader :one + + def write_many + @a = 1 + @b = 2 + @c = 3 + @d = 4 + @one = 1 + end + end + + foo = Extended.new + foo.write_many + foo.write_many + foo.write_many +} + +# test setinstancevariable on embedded objects +assert_equal '1', %q{ + class Embedded + attr_reader :one + + def write_one + @one = 1 + end + end + + foo = Embedded.new + foo.write_one + foo.write_one + foo.write_one +} + +# test setinstancevariable after extension +assert_equal '[10, 11, 12, 13, 1]', %q{ + class WillExtend + attr_reader :one + + def make_extended + @foo1 = 10 + @foo2 = 11 + @foo3 = 12 + @foo4 = 13 + end + + def write_one + @one = 1 + end + + def read_all + [@foo1, @foo2, @foo3, @foo4, @one] + end + end + + foo = WillExtend.new + foo.write_one + foo.write_one + foo.make_extended + foo.write_one + foo.read_all +} + +# test setinstancevariable on frozen object +assert_equal 'object was not modified', %q{ + class WillFreeze + def write + @ivar = 1 + end + end + + wf = WillFreeze.new + wf.write + wf.write + wf.freeze + + begin + wf.write + rescue FrozenError + "object was not modified" + end +} + +# Test getinstancevariable and inline caches +assert_equal '6', %q{ + class Foo + def initialize + @x1 = 1 + @x2 = 1 + @x2 = 1 + @x3 = 1 + @x4 = 3 + end + + def bar + x = 1 + @x4 + @x4 + end + end + + f = Foo.new + f.bar + f.bar +} + +# Test that getinstancevariable codegen checks for extended table size +assert_equal "nil\n", %q{ + class A + def read + @ins1000 + end + end + + ins = A.new + other = A.new + 10.times { other.instance_variable_set(:"@otr#{_1}", 'value') } + 1001.times { ins.instance_variable_set(:"@ins#{_1}", 'value') } + + ins.read + ins.read + ins.read + + p other.read +} + +# Test that opt_aref checks the class of the receiver +assert_equal 'special', %q{ + def foo(array) + array[30] + end + + foo([]) + foo([]) + + special = [] + def special.[](idx) + 'special' + end + + foo(special) +} + +# Test that object references in generated code get marked and moved +assert_equal "good", %q{ + def bar + "good" + end + + def foo + bar + end + + foo + foo + + begin + GC.verify_compaction_references(expand_heap: true, toward: :empty) + rescue NotImplementedError + # in case compaction isn't supported + end + + foo +} + +# Test polymorphic getinstancevariable. T_OBJECT -> T_STRING +assert_equal 'ok', %q{ + @hello = @h1 = @h2 = @h3 = @h4 = 'ok' + str = "" + str.instance_variable_set(:@hello, 'ok') + + public def get + @hello + end + + get + get + str.get + str.get +} + +# Test polymorphic getinstancevariable, two different classes +assert_equal 'ok', %q{ + class Embedded + def initialize + @ivar = 0 + end + + def get + @ivar + end + end + + class Extended < Embedded + def initialize + @v1 = @v2 = @v3 = @v4 = @ivar = 'ok' + end + end + + embed = Embedded.new + extend = Extended.new + + embed.get + embed.get + extend.get + extend.get +} + +# Test megamorphic getinstancevariable +assert_equal 'ok', %q{ + parent = Class.new do + def initialize + @hello = @h1 = @h2 = @h3 = @h4 = 'ok' + end + + def get + @hello + end + end + + subclasses = 300.times.map { Class.new(parent) } + subclasses.each { _1.new.get } + parent.new.get +} + +# Test polymorphic opt_aref. array -> hash +assert_equal '[42, :key]', %q{ + def index(obj, idx) + obj[idx] + end + + index([], 0) # get over compilation threshold + + [ + index([42], 0), + index({0=>:key}, 0), + ] +} + +# Test polymorphic opt_aref. hash -> array -> custom class +assert_equal '[nil, nil, :custom]', %q{ + def index(obj, idx) + obj[idx] + end + + custom = Object.new + def custom.[](_idx) + :custom + end + + index({}, 0) # get over compilation threshold + + [ + index({}, 0), + index([], 0), + index(custom, 0) + ] +} + +# Test polymorphic opt_aref. array -> custom class +assert_equal '[42, :custom]', %q{ + def index(obj, idx) + obj[idx] + end + + custom = Object.new + def custom.[](_idx) + :custom + end + + index([], 0) # get over compilation threshold + + [ + index([42], 0), + index(custom, 0) + ] +} + +# Test custom hash method with opt_aref +assert_equal '[nil, :ok]', %q{ + def index(obj, idx) + obj[idx] + end + + custom = Object.new + def custom.hash + 42 + end + + h = {custom => :ok} + + [ + index(h, 0), + index(h, custom) + ] +} + +# Test default value block for Hash with opt_aref +assert_equal '[42, :default]', %q{ + def index(obj, idx) + obj[idx] + end + + h = Hash.new { :default } + h[0] = 42 + + [ + index(h, 0), + index(h, 1) + ] +} + +# A regression test for making sure cfp->sp is proper when +# hitting stubs. See :stub-sp-flush: +assert_equal 'ok', %q{ + class D + def foo + Object.new + end + end + + GC.stress = true + 10.times do + D.new.foo + # ^ + # This hits a stub with sp_offset > 0 + end + + :ok +} + +# Test polymorphic callsite, cfunc -> iseq +assert_equal '[Cfunc, Iseq]', %q{ + public def call_itself + itself # the polymorphic callsite + end + + class Cfunc; end + + class Iseq + def itself + self + end + end + + call_itself # cross threshold + + [Cfunc.call_itself, Iseq.call_itself] +} + +# Test polymorphic callsite, iseq -> cfunc +assert_equal '[Iseq, Cfunc]', %q{ + public def call_itself + itself # the polymorphic callsite + end + + class Cfunc; end + + class Iseq + def itself + self + end + end + + call_itself # cross threshold + + [Iseq.call_itself, Cfunc.call_itself] +} + +# attr_reader method +assert_equal '[100, 299]', %q{ + class A + attr_reader :foo + + def initialize + @foo = 100 + end + + # Make it extended + def fill! + @bar = @jojo = @as = @sdfsdf = @foo = 299 + end + end + + def bar(ins) + ins.foo + end + + ins = A.new + oth = A.new + oth.fill! + + bar(ins) + bar(oth) + + [bar(ins), bar(oth)] +} + +# get ivar on object, then on hash +assert_equal '[42, 100]', %q{ + class Hash + attr_accessor :foo + end + + class A + attr_reader :foo + + def initialize + @foo = 42 + end + end + + def use(val) + val.foo + end + + + h = {} + h.foo = 100 + obj = A.new + + use(obj) + [use(obj), use(h)] +} + +# get ivar on String +assert_equal '[nil, nil, 42, 42]', %q{ + # @foo to exercise the getinstancevariable instruction + public def get_foo + @foo + end + + get_foo + get_foo # compile it for the top level object + + class String + attr_reader :foo + end + + def run + str = String.new + + getter = str.foo + insn = str.get_foo + + str.instance_variable_set(:@foo, 42) + + [getter, insn, str.foo, str.get_foo] + end + + run + run +} + +# splatting an empty array on a getter +assert_equal '42', %q{ + @foo = 42 + module Kernel + attr_reader :foo + end + + def run + foo(*[]) + end + + run + run +} + +# splatting an empty array on a specialized method +assert_equal 'ok', %q{ + def run + "ok".to_s(*[]) + end + + run + run +} + +# splatting an single element array on a specialized method +assert_equal '[1]', %q{ + def run + [].<<(*[1]) + end + + run + run +} + +# specialized method with wrong args +assert_equal 'ok', %q{ + def run(x) + "bad".to_s(123) if x + rescue + :ok + end + + run(false) + run(true) +} + +# getinstancevariable on Symbol +assert_equal '[nil, nil]', %q{ + # @foo to exercise the getinstancevariable instruction + public def get_foo + @foo + end + + dyn_sym = ("a" + "b").to_sym + sym = :static + + # compile get_foo + dyn_sym.get_foo + dyn_sym.get_foo + + [dyn_sym.get_foo, sym.get_foo] +} + +# attr_reader on Symbol +assert_equal '[nil, nil]', %q{ + class Symbol + attr_reader :foo + end + + public def get_foo + foo + end + + dyn_sym = ("a" + "b").to_sym + sym = :static + + # compile get_foo + dyn_sym.get_foo + dyn_sym.get_foo + + [dyn_sym.get_foo, sym.get_foo] +} + +# passing too few arguments to method with optional parameters +assert_equal 'raised', %q{ + def opt(a, b = 0) + end + + def use + opt + end + + use rescue nil + begin + use + :ng + rescue ArgumentError + :raised + end +} + +# passing too many arguments to method with optional parameters +assert_equal 'raised', %q{ + def opt(a, b = 0) + end + + def use + opt(1, 2, 3, 4) + end + + use rescue nil + begin + use + :ng + rescue ArgumentError + :raised + end +} + +# test calling Ruby method with a block +assert_equal '[1, 2, 42]', %q{ + def thing(a, b) + [a, b, yield] + end + + def use + thing(1,2) { 42 } + end + + use + use +} + +# test calling C method with a block +assert_equal '[42, 42]', %q{ + def use(array, initial) + array.reduce(initial) { |a, b| a + b } + end + + use([], 0) + [use([2, 2], 38), use([14, 14, 14], 0)] +} + +# test calling block param +assert_equal '[1, 2, 42]', %q{ + def foo(&block) + block.call + end + + [foo {1}, foo {2}, foo {42}] +} + +# test calling without block param +assert_equal '[1, false, 2, false]', %q{ + def bar + block_given? && yield + end + + def foo(&block) + bar(&block) + end + + [foo { 1 }, foo, foo { 2 }, foo] +} + +# test calling block param failing +assert_equal '42', %q{ + def foo(&block) + block.call + end + + foo {} # warmup + + begin + foo + rescue NoMethodError => e + 42 if nil == e.receiver + end +} + +# test calling method taking block param +assert_equal '[Proc, 1, 2, 3, Proc]', %q{ + def three(a, b, c, &block) + [a, b, c, block.class] + end + + def zero(&block) + block.class + end + + def use_three + three(1, 2, 3) {} + end + + def use_zero + zero {} + end + + use_three + use_zero + + [use_zero] + use_three +} + +# test building empty array +assert_equal '[]', %q{ + def build_arr + [] + end + + build_arr + build_arr +} + +# test building array of one element +assert_equal '[5]', %q{ + def build_arr(val) + [val] + end + + build_arr(5) + build_arr(5) +} + +# test building array of several element +assert_equal '[5, 5, 5, 5, 5]', %q{ + def build_arr(val) + [val, val, val, val, val] + end + + build_arr(5) + build_arr(5) +} + +# test building empty hash +assert_equal '{}', %q{ + def build_hash + {} + end + + build_hash + build_hash +} + +# test building hash with values +assert_equal '{:foo=>:bar}', %q{ + def build_hash(val) + { foo: val } + end + + build_hash(:bar) + build_hash(:bar) +} + +# test string interpolation with known types +assert_equal 'foobar', %q{ + def make_str + foo = -"foo" + bar = -"bar" + "#{foo}#{bar}" + end + + make_str + make_str +} + +# test string interpolation with unknown types +assert_equal 'foobar', %q{ + def make_str(foo, bar) + "#{foo}#{bar}" + end + + make_str("foo", "bar") + make_str("foo", "bar") +} + +# test string interpolation with known non-strings +assert_equal 'foo123', %q{ + def make_str + foo = -"foo" + bar = 123 + "#{foo}#{bar}" + end + + make_str + make_str +} + +# test string interpolation with unknown non-strings +assert_equal 'foo123', %q{ + def make_str(foo, bar) + "#{foo}#{bar}" + end + + make_str("foo", 123) + make_str("foo", 123) +} + +# test that invalidation of String#to_s doesn't crash +assert_equal 'meh', %q{ + def inval_method + "".to_s + end + + inval_method + + class String + def to_s + "meh" + end + end + + inval_method +} + +# test that overriding to_s on a String subclass works consistently +assert_equal 'meh', %q{ + class MyString < String + def to_s + "meh" + end + end + + def test_to_s(obj) + obj.to_s + end + + OBJ = MyString.new + + # Should return '' both times + test_to_s("") + test_to_s("") + + # Can return '' if YJIT optimises String#to_s too aggressively + test_to_s(OBJ) + test_to_s(OBJ) +} + +# test string interpolation with overridden to_s +assert_equal 'foo', %q{ + class String + def to_s + "bad" + end + end + + def make_str(foo) + "#{foo}" + end + + make_str("foo") + make_str("foo") +} + +# Test that String unary plus returns the same object ID for an unfrozen string. +assert_equal 'true', %q{ + def jittable_method + str = "bar" + + old_obj_id = str.object_id + uplus_str = +str + + uplus_str.object_id == old_obj_id + end + jittable_method +} + +# Test that String unary plus returns a different unfrozen string when given a frozen string +assert_equal 'false', %q{ + # Logic needs to be inside an ISEQ, such as a method, for YJIT to compile it + def jittable_method + frozen_str = "foo".freeze + + old_obj_id = frozen_str.object_id + uplus_str = +frozen_str + + uplus_str.object_id == old_obj_id || uplus_str.frozen? + end + + jittable_method +} + +# String-subclass objects should behave as expected inside string-interpolation via concatstrings +assert_equal 'monkeys / monkeys, yo!', %q{ + class MyString < String + # This is a terrible idea in production code, but we'd like YJIT to match CRuby + def to_s + super + ", yo!" + end + end + + def jittable_method + m = MyString.new('monkeys') + "#{m} / #{m.to_s}" + end + + jittable_method +} + +# String-subclass objects should behave as expected for string equality +assert_equal 'false', %q{ + class MyString < String + # This is a terrible idea in production code, but we'd like YJIT to match CRuby + def ==(b) + "#{self}_" == b + end + end + + def jittable_method + ma = MyString.new("a") + + # Check equality with string-subclass receiver + ma == "a" || ma != "a_" || + # Check equality with string receiver + "a_" == ma || "a" != ma || + # Check equality between string subclasses + ma != MyString.new("a_") || + # Make sure "string always equals itself" check isn't used with overridden equality + ma == ma + end + jittable_method +} + +# Test to_s duplicates a string subclass object but not a string +assert_equal 'false', %q{ + class MyString < String; end + + def jittable_method + a = "a" + ma = MyString.new("a") + + a.object_id != a.to_s.object_id || + ma.object_id == ma.to_s.object_id + end + jittable_method +} + +# Test freeze on string subclass +assert_equal 'true', %q{ + class MyString < String; end + + def jittable_method + fma = MyString.new("a").freeze + + # Freezing a string subclass should not duplicate it + fma.object_id == fma.freeze.object_id + end + jittable_method +} + +# Test unary minus on string subclass +assert_equal 'true', %q{ + class MyString < String; end + + def jittable_method + ma = MyString.new("a") + fma = MyString.new("a").freeze + + # Unary minus on frozen string subclass should not duplicate it + fma.object_id == (-fma).object_id && + # Unary minus on unfrozen string subclass should duplicate it + ma.object_id != (-ma).object_id + end + jittable_method +} + +# Test unary plus on string subclass +assert_equal 'true', %q{ + class MyString < String; end + + def jittable_method + fma = MyString.new("a").freeze + + # Unary plus on frozen string subclass should not duplicate it + fma.object_id != (+fma).object_id + end + jittable_method +} + +# Test << operator on string subclass +assert_equal 'abab', %q{ + class MyString < String; end + + def jittable_method + a = -"a" + mb = MyString.new("b") + + buf = String.new + mbuf = MyString.new + + buf << a << mb + mbuf << a << mb + + buf + mbuf + end + jittable_method +} + +# test invokebuiltin as used in struct assignment +assert_equal '123', %q{ + def foo(obj) + obj.foo = 123 + end + + struct = Struct.new(:foo) + obj = struct.new + foo(obj) + foo(obj) +} + +# test invokebuiltin_delegate as used inside Dir.open +assert_equal '.', %q{ + def foo(path) + Dir.open(path).path + end + + foo(".") + foo(".") +} + +# test invokebuiltin_delegate_leave in method called from jit +assert_normal_exit %q{ + def foo(obj) + obj.clone + end + + foo(Object.new) + foo(Object.new) +} + +# test invokebuiltin_delegate_leave in method called from cfunc +assert_normal_exit %q{ + def foo(obj) + [obj].map(&:clone) + end + + foo(Object.new) + foo(Object.new) +} + +# defining TrueClass#! +assert_equal '[false, false, :ok]', %q{ + def foo(obj) + !obj + end + + x = foo(true) + y = foo(true) + + class TrueClass + def ! + :ok + end + end + + z = foo(true) + + [x, y, z] +} + +# defining FalseClass#! +assert_equal '[true, true, :ok]', %q{ + def foo(obj) + !obj + end + + x = foo(false) + y = foo(false) + + class FalseClass + def ! + :ok + end + end + + z = foo(false) + + [x, y, z] +} + +# defining NilClass#! +assert_equal '[true, true, :ok]', %q{ + def foo(obj) + !obj + end + + x = foo(nil) + y = foo(nil) + + class NilClass + def ! + :ok + end + end + + z = foo(nil) + + [x, y, z] +} + +# polymorphic opt_not +assert_equal '[true, true, false, false, false, false, false]', %q{ + def foo(obj) + !obj + end + + foo(0) + [foo(nil), foo(false), foo(true), foo([]), foo(0), foo(4.2), foo(:sym)] +} + +# getlocal with 2 levels +assert_equal '7', %q{ + def foo(foo, bar) + while foo > 0 + while bar > 0 + return foo + bar + end + end + end + + foo(5,2) + foo(5,2) +} + +# test pattern matching +assert_equal '[:ok, :ok]', %q{ + class C + def destructure_keys + {} + end + end + + pattern_match = ->(i) do + case i + in a: 0 + :ng + else + :ok + end + end + + [{}, C.new].map(&pattern_match) +} + +# Call to object with singleton +assert_equal '123', %q{ + obj = Object.new + def obj.foo + 123 + end + + def foo(obj) + obj.foo() + end + + foo(obj) + foo(obj) +} + +# Call method on an object that has a non-material +# singleton class. +# TODO: assert that it takes no side exits? This +# test case revealed that we were taking exits unnecessarily. +assert_normal_exit %q{ + def foo(obj) + obj.itself + end + + o = Object.new.singleton_class + foo(o) + foo(o) +} + +# Call to singleton class +assert_equal '123', %q{ + class Foo + def self.foo + 123 + end + end + + def foo(obj) + obj.foo() + end + + foo(Foo) + foo(Foo) +} + +# invokesuper edge case +assert_equal '[:A, [:A, :B]]', %q{ + class B + def foo = :B + end + + class A < B + def foo = [:A, super()] + end + + A.new.foo + A.new.foo # compile A#foo + + class C < A + define_method(:bar, A.instance_method(:foo)) + end + + C.new.bar +} + +# Same invokesuper bytecode, multiple destinations +assert_equal '[:Forward, :SecondTerminus]', %q{ + module Terminus + def foo = :Terminus + end + + module SecondTerminus + def foo = :SecondTerminus + end + + + module Forward + def foo = [:Forward, super] + end + + class B + include SecondTerminus + end + + class A < B + include Terminus + include Forward + end + + A.new.foo + A.new.foo # compile + + class B + include Forward + alias bar foo + end + + # A.ancestors.take(5) == [A, Forward, Terminus, B, Forward, SecondTerminus] + + A.new.bar +} + +# invokesuper calling into itself +assert_equal '[:B, [:B, :m]]', %q{ + module M + def foo = :m + end + + class B + include M + def foo = [:B, super] + end + + ins = B.new + ins.singleton_class # materialize the singleton class + ins.foo + ins.foo # compile + + ins.singleton_class.define_method(:bar, B.instance_method(:foo)) + ins.bar +} + +# invokesuper changed ancestor +assert_equal '[:A, [:M, :B]]', %q{ + class B + def foo + :B + end + end + + class A < B + def foo + [:A, super] + end + end + + module M + def foo + [:M, super] + end + end + + ins = A.new + ins.foo + ins.foo + A.include(M) + ins.foo +} + +# invokesuper changed ancestor via prepend +assert_equal '[:A, [:M, :B]]', %q{ + class B + def foo + :B + end + end + + class A < B + def foo + [:A, super] + end + end + + module M + def foo + [:M, super] + end + end + + ins = A.new + ins.foo + ins.foo + B.prepend(M) + ins.foo +} + +# invokesuper replaced method +assert_equal '[:A, :Btwo]', %q{ + class B + def foo + :B + end + end + + class A < B + def foo + [:A, super] + end + end + + ins = A.new + ins.foo + ins.foo + class B + def foo + :Btwo + end + end + ins.foo +} + +# Call to fixnum +assert_equal '[true, false]', %q{ + def is_odd(obj) + obj.odd? + end + + is_odd(1) + is_odd(1) + + [is_odd(123), is_odd(456)] +} + +# Call to bignum +assert_equal '[true, false]', %q{ + def is_odd(obj) + obj.odd? + end + + bignum = 99999999999999999999 + is_odd(bignum) + is_odd(bignum) + + [is_odd(bignum), is_odd(bignum+1)] +} + +# Call to fixnum and bignum +assert_equal '[true, false, true, false]', %q{ + def is_odd(obj) + obj.odd? + end + + bignum = 99999999999999999999 + is_odd(bignum) + is_odd(bignum) + is_odd(123) + is_odd(123) + + [is_odd(123), is_odd(456), is_odd(bignum), is_odd(bignum+1)] +} + +# Call to static and dynamic symbol +assert_equal 'bar', %q{ + def to_string(obj) + obj.to_s + end + + to_string(:foo) + to_string(:foo) + to_string((-"bar").to_sym) + to_string((-"bar").to_sym) +} + +# Call to flonum and heap float +assert_equal '[nil, nil, nil, 1]', %q{ + def is_inf(obj) + obj.infinite? + end + + is_inf(0.0) + is_inf(0.0) + is_inf(1e256) + is_inf(1e256) + + [ + is_inf(0.0), + is_inf(1.0), + is_inf(1e256), + is_inf(1.0/0.0) + ] +} + +assert_equal '[1, 2, 3, 4, 5]', %q{ + def splatarray + [*(1..5)] + end + + splatarray + splatarray +} + +assert_equal '[1, 1, 2, 1, 2, 3]', %q{ + def expandarray + arr = [1, 2, 3] + + a, = arr + b, c, = arr + d, e, f = arr + + [a, b, c, d, e, f] + end + + expandarray + expandarray +} + +assert_equal '[1, 1]', %q{ + def expandarray_useless_splat + arr = (1..10).to_a + + a, * = arr + b, (*) = arr + + [a, b] + end + + expandarray_useless_splat + expandarray_useless_splat +} + +assert_equal '[:not_heap, nil, nil]', %q{ + def expandarray_not_heap + a, b, c = :not_heap + [a, b, c] + end + + expandarray_not_heap + expandarray_not_heap +} + +assert_equal '[:not_array, nil, nil]', %q{ + def expandarray_not_array(obj) + a, b, c = obj + [a, b, c] + end + + obj = Object.new + def obj.to_ary + [:not_array] + end + + expandarray_not_array(obj) + expandarray_not_array(obj) +} + +assert_equal '[1, 2, nil]', %q{ + def expandarray_rhs_too_small + a, b, c = [1, 2] + [a, b, c] + end + + expandarray_rhs_too_small + expandarray_rhs_too_small +} + +assert_equal '[1, [2]]', %q{ + def expandarray_splat + a, *b = [1, 2] + [a, b] + end + + expandarray_splat + expandarray_splat +} + +assert_equal '2', %q{ + def expandarray_postarg + *, a = [1, 2] + a + end + + expandarray_postarg + expandarray_postarg +} + +assert_equal '10', %q{ + obj = Object.new + val = nil + obj.define_singleton_method(:to_ary) { val = 10; [] } + + def expandarray_always_call_to_ary(object) + * = object + end + + expandarray_always_call_to_ary(obj) + expandarray_always_call_to_ary(obj) + + val +} + +# regression test of local type change +assert_equal '1.1', %q{ +def bar(baz, quux) + if baz.integer? + baz, quux = quux, nil + end + baz.to_s +end + +bar(123, 1.1) +bar(123, 1.1) +} + +# test enabling a line TracePoint in a C method call +assert_equal '[[:line, true]]', %q{ + events = [] + events.instance_variable_set( + :@tp, + TracePoint.new(:line) { |tp| events << [tp.event, tp.lineno] if tp.path == __FILE__ } + ) + def events.to_str + @tp.enable; '' + end + + # Stay in generated code while enabling tracing + def events.compiled(obj) + String(obj) + @tp.disable; __LINE__ + end + + line = events.compiled(events) + events[0][-1] = (events[0][-1] == line) + + events +} + +# test enabling a c_return TracePoint in a C method call +assert_equal '[[:c_return, :String, :string_alias, "events_to_str"]]', %q{ + events = [] + events.instance_variable_set(:@tp, TracePoint.new(:c_return) { |tp| events << [tp.event, tp.method_id, tp.callee_id, tp.return_value] }) + def events.to_str + @tp.enable; 'events_to_str' + end + + # Stay in generated code while enabling tracing + alias string_alias String + def events.compiled(obj) + string_alias(obj) + @tp.disable + end + + events.compiled(events) + + events +} unless defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # MJIT calls extra Ruby methods + +# test enabling a TracePoint that targets a particular line in a C method call +assert_equal '[true]', %q{ + events = [] + events.instance_variable_set(:@tp, TracePoint.new(:line) { |tp| events << tp.lineno }) + def events.to_str + @tp.enable(target: method(:compiled)) + '' + end + + # Stay in generated code while enabling tracing + def events.compiled(obj) + String(obj) + __LINE__ + end + + line = events.compiled(events) + events[0] = (events[0] == line) + + events +} + +# test enabling tracing in the middle of splatarray +assert_equal '[true]', %q{ + events = [] + obj = Object.new + obj.instance_variable_set(:@tp, TracePoint.new(:line) { |tp| events << tp.lineno }) + def obj.to_a + @tp.enable(target: method(:compiled)) + [] + end + + # Enable tracing in the middle of the splatarray instruction + def obj.compiled(obj) + * = *obj + __LINE__ + end + + obj.compiled([]) + line = obj.compiled(obj) + events[0] = (events[0] == line) + + events +} + +# test enabling tracing in the middle of opt_aref. Different since the codegen +# for it ends in a jump. +assert_equal '[true]', %q{ + def lookup(hash, tp) + hash[42] + tp.disable; __LINE__ + end + + lines = [] + tp = TracePoint.new(:line) { lines << _1.lineno if _1.path == __FILE__ } + + lookup(:foo, tp) + lookup({}, tp) + + enable_tracing_on_missing = Hash.new { tp.enable } + + expected_line = lookup(enable_tracing_on_missing, tp) + + lines[0] = true if lines[0] == expected_line + + lines +} + +# test enabling c_call tracing before compiling +assert_equal '[[:c_call, :itself]]', %q{ + def shouldnt_compile + itself + end + + events = [] + tp = TracePoint.new(:c_call) { |tp| events << [tp.event, tp.method_id] } + + # assume first call compiles + tp.enable { shouldnt_compile } + + events +} unless defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # MJIT calls extra Ruby methods + +# test enabling c_return tracing before compiling +assert_equal '[[:c_return, :itself, main]]', %q{ + def shouldnt_compile + itself + end + + events = [] + tp = TracePoint.new(:c_return) { |tp| events << [tp.event, tp.method_id, tp.return_value] } + + # assume first call compiles + tp.enable { shouldnt_compile } + + events +} unless defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # MJIT calls extra Ruby methods + +# test c_call invalidation +assert_equal '[[:c_call, :itself]]', %q{ + # enable the event once to make sure invalidation + # happens the second time we enable it + TracePoint.new(:c_call) {}.enable{} + + def compiled + itself + end + + # assume first call compiles + compiled + + events = [] + tp = TracePoint.new(:c_call) { |tp| events << [tp.event, tp.method_id] } + tp.enable { compiled } + + events +} + +# test enabling tracing for a suspended fiber +assert_equal '[[:return, 42]]', %q{ + def traced_method + Fiber.yield + 42 + end + + events = [] + tp = TracePoint.new(:return) { events << [_1.event, _1.return_value] } + # assume first call compiles + fiber = Fiber.new { traced_method } + fiber.resume + tp.enable(target: method(:traced_method)) + fiber.resume + + events +} + +# test compiling on non-tracing ractor then running on a tracing one +assert_equal '[:itself]', %q{ + def traced_method + itself + end + + tracing_ractor = Ractor.new do + # 1: start tracing + events = [] + tp = TracePoint.new(:c_call) { events << _1.method_id } + tp.enable + Ractor.yield(nil) + + # 3: run compiled method on tracing ractor + Ractor.yield(nil) + traced_method + + events + ensure + tp&.disable + end + + tracing_ractor.take + + # 2: compile on non tracing ractor + traced_method + + tracing_ractor.take + tracing_ractor.take +} + +# Try to hit a lazy branch stub while another ractor enables tracing +assert_equal '42', %q{ + def compiled(arg) + if arg + arg + 1 + else + itself + itself + end + end + + ractor = Ractor.new do + compiled(false) + Ractor.yield(nil) + compiled(41) + end + + tp = TracePoint.new(:line) { itself } + ractor.take + tp.enable + + ractor.take +} + +# Test equality with changing types +assert_equal '[true, false, false, false]', %q{ + def eq(a, b) + a == b + end + + [ + eq("foo", "foo"), + eq("foo", "bar"), + eq(:foo, "bar"), + eq("foo", :bar) + ] +} + +# Redefined String eq +assert_equal 'true', %q{ + class String + def ==(other) + true + end + end + + def eq(a, b) + a == b + end + + eq("foo", "bar") + eq("foo", "bar") +} + +# Redefined Integer eq +assert_equal 'true', %q{ + class Integer + def ==(other) + true + end + end + + def eq(a, b) + a == b + end + + eq(1, 2) + eq(1, 2) +} + +# aset on array with invalid key +assert_normal_exit %q{ + def foo(arr) + arr[:foo] = 123 + end + + foo([1]) rescue nil + foo([1]) rescue nil +} + +# test ractor exception on when setting ivar +assert_equal '42', %q{ + class A + def self.foo + _foo = 1 + _bar = 2 + begin + @bar = _foo + _bar + rescue Ractor::IsolationError + 42 + end + end + end + + A.foo + A.foo + + Ractor.new { A.foo }.take +} + +assert_equal '["plain", "special", "sub", "plain"]', %q{ + def foo(arg) + arg.to_s + end + + class Sub < String + end + + special = String.new("special") + special.singleton_class + + [ + foo("plain"), + foo(special), + foo(Sub.new("sub")), + foo("plain") + ] +} + +assert_equal '["sub", "sub"]', %q{ + def foo(arg) + arg.to_s + end + + class Sub < String + def to_s + super + end + end + + sub = Sub.new("sub") + + [foo(sub), foo(sub)] +} + +assert_equal '[1]', %q{ + def kwargs(value:) + value + end + + 5.times.map { kwargs(value: 1) }.uniq +} + +assert_equal '[:ok]', %q{ + def kwargs(value:) + value + end + + 5.times.map { kwargs() rescue :ok }.uniq +} + +assert_equal '[:ok]', %q{ + def kwargs(a:, b: nil) + value + end + + 5.times.map { kwargs(b: 123) rescue :ok }.uniq +} + +assert_equal '[[1, 2]]', %q{ + def kwargs(left:, right:) + [left, right] + end + + 5.times.flat_map do + [ + kwargs(left: 1, right: 2), + kwargs(right: 2, left: 1) + ] + end.uniq +} + +assert_equal '[[1, 2]]', %q{ + def kwargs(lead, kwarg:) + [lead, kwarg] + end + + 5.times.map { kwargs(1, kwarg: 2) }.uniq +} + +# optional and keyword args +assert_equal '[[1, 2, 3]]', %q{ + def opt_and_kwargs(a, b=2, c: nil) + [a,b,c] + end + + 5.times.map { opt_and_kwargs(1, c: 3) }.uniq +} + +assert_equal '[[1, 2, 3]]', %q{ + def opt_and_kwargs(a, b=nil, c: nil) + [a,b,c] + end + + 5.times.map { opt_and_kwargs(1, 2, c: 3) }.uniq +} + +# Bug #18453 +assert_equal '[[1, nil, 2]]', %q{ + def opt_and_kwargs(a = {}, b: nil, c: nil) + [a, b, c] + end + + 5.times.map { opt_and_kwargs(1, c: 2) }.uniq +} + +assert_equal '[[{}, nil, 1]]', %q{ + def opt_and_kwargs(a = {}, b: nil, c: nil) + [a, b, c] + end + + 5.times.map { opt_and_kwargs(c: 1) }.uniq +} + +# leading and keyword arguments are swapped into the right order +assert_equal '[[1, 2, 3, 4, 5, 6]]', %q{ + def kwargs(five, six, a:, b:, c:, d:) + [a, b, c, d, five, six] + end + + 5.times.flat_map do + [ + kwargs(5, 6, a: 1, b: 2, c: 3, d: 4), + kwargs(5, 6, a: 1, b: 2, d: 4, c: 3), + kwargs(5, 6, a: 1, c: 3, b: 2, d: 4), + kwargs(5, 6, a: 1, c: 3, d: 4, b: 2), + kwargs(5, 6, a: 1, d: 4, b: 2, c: 3), + kwargs(5, 6, a: 1, d: 4, c: 3, b: 2), + kwargs(5, 6, b: 2, a: 1, c: 3, d: 4), + kwargs(5, 6, b: 2, a: 1, d: 4, c: 3), + kwargs(5, 6, b: 2, c: 3, a: 1, d: 4), + kwargs(5, 6, b: 2, c: 3, d: 4, a: 1), + kwargs(5, 6, b: 2, d: 4, a: 1, c: 3), + kwargs(5, 6, b: 2, d: 4, c: 3, a: 1), + kwargs(5, 6, c: 3, a: 1, b: 2, d: 4), + kwargs(5, 6, c: 3, a: 1, d: 4, b: 2), + kwargs(5, 6, c: 3, b: 2, a: 1, d: 4), + kwargs(5, 6, c: 3, b: 2, d: 4, a: 1), + kwargs(5, 6, c: 3, d: 4, a: 1, b: 2), + kwargs(5, 6, c: 3, d: 4, b: 2, a: 1), + kwargs(5, 6, d: 4, a: 1, b: 2, c: 3), + kwargs(5, 6, d: 4, a: 1, c: 3, b: 2), + kwargs(5, 6, d: 4, b: 2, a: 1, c: 3), + kwargs(5, 6, d: 4, b: 2, c: 3, a: 1), + kwargs(5, 6, d: 4, c: 3, a: 1, b: 2), + kwargs(5, 6, d: 4, c: 3, b: 2, a: 1) + ] + end.uniq +} + +# implicit hashes get skipped and don't break compilation +assert_equal '[[:key]]', %q{ + def implicit(hash) + hash.keys + end + + 5.times.map { implicit(key: :value) }.uniq +} + +# default values on keywords don't mess up argument order +assert_equal '[2]', %q{ + def default_value + 1 + end + + def default_expression(value: default_value) + value + end + + 5.times.map { default_expression(value: 2) }.uniq +} + +# constant default values on keywords +assert_equal '[3]', %q{ + def default_expression(value: 3) + value + end + + 5.times.map { default_expression }.uniq +} + +# non-constant default values on keywords +assert_equal '[3]', %q{ + def default_value + 3 + end + + def default_expression(value: default_value) + value + end + + 5.times.map { default_expression }.uniq +} + +# reordered optional kwargs +assert_equal '[[100, 1]]', %q{ + def foo(capacity: 100, max: nil) + [capacity, max] + end + + 5.times.map { foo(max: 1) }.uniq +} + +# invalid lead param +assert_equal 'ok', %q{ + def bar(baz: 2) + baz + end + + def foo + bar(1, baz: 123) + end + + begin + foo + foo + rescue ArgumentError => e + print "ok" + end +} + +# reordered required kwargs +assert_equal '[[1, 2, 3, 4]]', %q{ + def foo(default1: 1, required1:, default2: 3, required2:) + [default1, required1, default2, required2] + end + + 5.times.map { foo(required1: 2, required2: 4) }.uniq +} + +# reordered default expression kwargs +assert_equal '[[:one, :two, 3]]', %q{ + def foo(arg1: (1+0), arg2: (2+0), arg3: (3+0)) + [arg1, arg2, arg3] + end + + 5.times.map { foo(arg2: :two, arg1: :one) }.uniq +} + +# complex kwargs +assert_equal '[[1, 2, 3, 4]]', %q{ + def foo(required:, specified: 999, simple_default: 3, complex_default: "4".to_i) + [required, specified, simple_default, complex_default] + end + + 5.times.map { foo(specified: 2, required: 1) }.uniq +} + +# cfunc kwargs +assert_equal '{:foo=>123}', %q{ + def foo(bar) + bar.store(:value, foo: 123) + bar[:value] + end + + foo({}) + foo({}) +} + +# cfunc kwargs +assert_equal '{:foo=>123}', %q{ + def foo(bar) + bar.replace(foo: 123) + end + + foo({}) + foo({}) +} + +# cfunc kwargs +assert_equal '{:foo=>123, :bar=>456}', %q{ + def foo(bar) + bar.replace(foo: 123, bar: 456) + end + + foo({}) + foo({}) +} + +# variadic cfunc kwargs +assert_equal '{:foo=>123}', %q{ + def foo(bar) + bar.merge(foo: 123) + end + + foo({}) + foo({}) +} + +# optimized cfunc kwargs +assert_equal 'false', %q{ + def foo + :foo.eql?(foo: :foo) + end + + foo + foo +} + +# attr_reader on frozen object +assert_equal 'false', %q{ + class Foo + attr_reader :exception + + def failed? + !exception.nil? + end + end + + foo = Foo.new.freeze + foo.failed? + foo.failed? +} + +# regression test for doing kwarg shuffle before checking for interrupts +assert_equal 'ok', %q{ + def new_media_drop(attributes:, product_drop:, context:, sources:) + nil.nomethod rescue nil # force YJIT to bail to side exit + + [attributes, product_drop, context, sources] + end + + def load_medias(product_drop: nil, raw_medias:, context:) + raw_medias.map do |raw_media| + case new_media_drop(context: context, attributes: raw_media, product_drop: product_drop, sources: []) + in [Hash, ProductDrop, Context, Array] + else + raise "bad shuffle" + end + end + end + + class Context; end + + class ProductDrop + attr_reader :title + def initialize(title) + @title = title + end + end + + # Make a thread so we have thread switching interrupts + th = Thread.new do + while true; end + end + 1_000.times do |i| + load_medias(product_drop: ProductDrop.new("foo"), raw_medias: [{}, {}], context: Context.new) + end + th.kill.join + + :ok +} + +# regression test for tracing attr_accessor methods. +assert_equal "true", %q{ + c = Class.new do + attr_accessor :x + alias y x + alias y= x= + end + obj = c.new + + ar_meth = obj.method(:x) + aw_meth = obj.method(:x=) + aar_meth = obj.method(:y) + aaw_meth = obj.method(:y=) + events = [] + trace = TracePoint.new(:c_call, :c_return){|tp| + next if tp.path != __FILE__ + next if tp.method_id == :call + case tp.event + when :c_call + events << [tp.event, tp.method_id, tp.callee_id] + when :c_return + events << [tp.event, tp.method_id, tp.callee_id, tp.return_value] + end + } + test_proc = proc do + obj.x = 1 + obj.x + obj.y = 2 + obj.y + aw_meth.call(1) + ar_meth.call + aaw_meth.call(2) + aar_meth.call + end + test_proc.call # populate call caches + trace.enable(&test_proc) + expected = [ + [:c_call, :x=, :x=], + [:c_return, :x=, :x=, 1], + [:c_call, :x, :x], + [:c_return, :x, :x, 1], + [:c_call, :x=, :y=], + [:c_return, :x=, :y=, 2], + [:c_call, :x, :y], + [:c_return, :x, :y, 2], + ] * 2 + + expected == events +} + +# duphash +assert_equal '{:foo=>123}', %q{ + def foo + {foo: 123} + end + + foo + foo +} + +# newhash +assert_equal '{:foo=>2}', %q{ + def foo + {foo: 1+1} + end + + foo + foo +} + +# block invalidation edge case +assert_equal 'undef', %q{ + class A + def foo(arg) + arg.times { A.remove_method(:bar) } + self + end + + def bar + 4 + end + + def use(arg) + # two consecutive sends. When bar is removed, the return address + # for calling it is already on foo's control frame + foo(arg).bar + rescue NoMethodError + :undef + end + end + + A.new.use 0 + A.new.use 0 + A.new.use 1 +} + +# block invalidation edge case +assert_equal 'ok', %q{ + class A + Good = :ng + def foo(arg) + arg.times { A.const_set(:Good, :ok) } + self + end + + def id(arg) + arg + end + + def use(arg) + # send followed by an opt_getinlinecache. + # The return address remains on the control frame + # when opt_getinlinecache is invalidated. + foo(arg).id(Good) + end + end + + A.new.use 0 + A.new.use 0 + A.new.use 1 +} + +assert_equal 'ok', %q{ + # test hitting a branch stub when out of memory + def nimai(jita) + if jita + :ng + else + :ok + end + end + + nimai(true) + nimai(true) + + RubyVM::YJIT.simulate_oom! if defined?(RubyVM::YJIT) + + nimai(false) +} + +assert_equal 'new', %q{ + # test block invalidation while out of memory + def foo + :old + end + + def test + foo + end + + def bar + :bar + end + + + test + test + + RubyVM::YJIT.simulate_oom! if defined?(RubyVM::YJIT) + + # Old simulat_omm! leaves one byte of space and this fills it up + bar + bar + + def foo + :new + end + + test +} + +assert_equal 'ok', %q{ + # Try to compile new method while OOM + def foo + :ok + end + + RubyVM::YJIT.simulate_oom! if defined?(RubyVM::YJIT) + + foo + foo +} + +# struct aref embedded +assert_equal '2', %q{ + def foo(s) + s.foo + end + + S = Struct.new(:foo) + foo(S.new(1)) + foo(S.new(2)) +} + +# struct aref non-embedded +assert_equal '4', %q{ + def foo(s) + s.d + end + + S = Struct.new(:a, :b, :c, :d, :e) + foo(S.new(1,2,3,4,5)) + foo(S.new(1,2,3,4,5)) +} + +# struct aset embedded +assert_equal '123', %q{ + def foo(s) + s.foo = 123 + end + + s = Struct.new(:foo).new + foo(s) + s = Struct.new(:foo).new + foo(s) + s.foo +} + +# struct aset non-embedded +assert_equal '[1, 2, 3, 4, 5]', %q{ + def foo(s) + s.a = 1 + s.b = 2 + s.c = 3 + s.d = 4 + s.e = 5 + end + + S = Struct.new(:a, :b, :c, :d, :e) + s = S.new + foo(s) + s = S.new + foo(s) + [s.a, s.b, s.c, s.d, s.e] +} + +# struct aref too many args +assert_equal 'ok', %q{ + def foo(s) + s.foo(:bad) + end + + s = Struct.new(:foo).new + foo(s) rescue :ok + foo(s) rescue :ok +} + +# struct aset too many args +assert_equal 'ok', %q{ + def foo(s) + s.set_foo(123, :bad) + end + + s = Struct.new(:foo) do + alias :set_foo :foo= + end + foo(s) rescue :ok + foo(s) rescue :ok +} + +# File.join is a cfunc accepting variable arguments as a Ruby array (argc = -2) +assert_equal 'foo/bar', %q{ + def foo + File.join("foo", "bar") + end + + foo + foo +} + +# File.join is a cfunc accepting variable arguments as a Ruby array (argc = -2) +assert_equal '', %q{ + def foo + File.join() + end + + foo + foo +} + +# Make sure we're correctly reading RStruct's as.ary union for embedded RStructs +assert_equal '3,12', %q{ + pt_struct = Struct.new(:x, :y) + p = pt_struct.new(3, 12) + def pt_inspect(pt) + "#{pt.x},#{pt.y}" + end + + # Make sure pt_inspect is JITted + 10.times { pt_inspect(p) } + + # Make sure it's returning '3,12' instead of e.g. '3,false' + pt_inspect(p) +} + +# Regression test for deadlock between branch_stub_hit and ractor_receive_if +assert_equal '10', %q{ + r = Ractor.new Ractor.current do |main| + main << 1 + main << 2 + main << 3 + main << 4 + main << 5 + main << 6 + main << 7 + main << 8 + main << 9 + main << 10 + end + + a = [] + a << Ractor.receive_if{|msg| msg == 10} + a << Ractor.receive_if{|msg| msg == 9} + a << Ractor.receive_if{|msg| msg == 8} + a << Ractor.receive_if{|msg| msg == 7} + a << Ractor.receive_if{|msg| msg == 6} + a << Ractor.receive_if{|msg| msg == 5} + a << Ractor.receive_if{|msg| msg == 4} + a << Ractor.receive_if{|msg| msg == 3} + a << Ractor.receive_if{|msg| msg == 2} + a << Ractor.receive_if{|msg| msg == 1} + + a.length +} + +# checktype +assert_equal 'false', %q{ + def function() + [1, 2] in [Integer, String] + end + function() +} + +# opt_send_without_block (VM_METHOD_TYPE_ATTRSET) +assert_equal 'foo', %q{ + class Foo + attr_writer :foo + + def foo() + self.foo = "foo" + end + end + foo = Foo.new + foo.foo +} + +# anytostring, intern +assert_equal 'true', %q{ + def foo() + :"#{true}" + end + foo() +} + +# toregexp, objtostring +assert_equal '/true/', %q{ + def foo() + /#{true}/ + end + foo().inspect +} + +# concatstrings, objtostring +assert_equal '9001', %q{ + def foo() + "#{9001}" + end + foo() +} + +# opt_send_without_block (VM_METHOD_TYPE_CFUNC) +assert_equal 'nil', %q{ + def foo + nil.inspect # argc: 0 + end + foo +} +assert_equal '4', %q{ + def foo + 2.pow(2) # argc: 1 + end + foo +} +assert_equal 'aba', %q{ + def foo + "abc".tr("c", "a") # argc: 2 + end + foo +} +assert_equal 'true', %q{ + def foo + respond_to?(:inspect) # argc: -1 + end + foo +} +assert_equal '["a", "b"]', %q{ + def foo + "a\nb".lines(chomp: true) # kwargs + end + foo +} + +# invokebuiltin +assert_equal '123', %q{ + def foo(obj) + obj.foo = 123 + end + + struct = Struct.new(:foo) + obj = struct.new + foo(obj) +} + +# invokebuiltin_delegate +assert_equal '.', %q{ + def foo(path) + Dir.open(path).path + end + foo(".") +} + +# opt_invokebuiltin_delegate_leave +assert_equal '[0]', %q{"\x00".unpack("c")} + +# opt_send_without_block (VM_METHOD_TYPE_ISEQ) +assert_equal '1', %q{ + def foo = 1 + def bar = foo + bar +} +assert_equal '[1, 2, 3]', %q{ + def foo(a, b) = [1, a, b] + def bar = foo(2, 3) + bar +} +assert_equal '[1, 2, 3, 4, 5, 6]', %q{ + def foo(a, b, c:, d:, e: 0, f: 6) = [a, b, c, d, e, f] + def bar = foo(1, 2, c: 3, d: 4, e: 5) + bar +} +assert_equal '[1, 2, 3, 4]', %q{ + def foo(a, b = 2) = [a, b] + def bar = foo(1) + foo(3, 4) + bar +} + +assert_equal '1', %q{ + def foo(a) = a + def bar = foo(1) { 2 } + bar +} +assert_equal '[1, 2]', %q{ + def foo(a, &block) = [a, block.call] + def bar = foo(1) { 2 } + bar +} + +# opt_send_without_block (VM_METHOD_TYPE_IVAR) +assert_equal 'foo', %q{ + class Foo + attr_reader :foo + + def initialize + @foo = "foo" + end + end + Foo.new.foo +} + +# opt_send_without_block (VM_METHOD_TYPE_OPTIMIZED) +assert_equal 'foo', %q{ + Foo = Struct.new(:bar) + Foo.new("bar").bar = "foo" +} +assert_equal 'foo', %q{ + Foo = Struct.new(:bar) + Foo.new("foo").bar +} + +# getblockparamproxy +assert_equal 'foo', %q{ + def foo(&block) + block.call + end + foo { "foo" } +} + +# getblockparam +assert_equal 'foo', %q{ + def foo(&block) + block + end + foo { "foo" }.call +} + +assert_equal '[1, 2]', %q{ + def foo + x = [2] + [1, *x] + end + + foo + foo +} + +# respond_to? with changing symbol +assert_equal 'false', %q{ + def foo(name) + :sym.respond_to?(name) + end + foo(:to_s) + foo(:to_s) + foo(:not_exist) +} + +# respond_to? with method being defined +assert_equal 'true', %q{ + def foo + :sym.respond_to?(:not_yet_defined) + end + foo + foo + module Kernel + def not_yet_defined = true + end + foo +} + +# respond_to? with undef method +assert_equal 'false', %q{ + module Kernel + def to_be_removed = true + end + def foo + :sym.respond_to?(:to_be_removed) + end + foo + foo + class Object + undef_method :to_be_removed + end + foo +} + +# respond_to? with respond_to_missing? +assert_equal 'true', %q{ + class Foo + end + def foo(x) + x.respond_to?(:bar) + end + foo(Foo.new) + foo(Foo.new) + class Foo + def respond_to_missing?(*) = true + end + foo(Foo.new) +} + +# bmethod +assert_equal '[1, 2, 3]', %q{ + one = 1 + define_method(:foo) do + one + end + + 3.times.map { |i| foo + i } +} + +# return inside bmethod +assert_equal 'ok', %q{ + define_method(:foo) do + 1.tap { return :ok } + end + + foo +} + +# bmethod optional and keywords +assert_equal '[[1, nil, 2]]', %q{ + define_method(:opt_and_kwargs) do |a = {}, b: nil, c: nil| + [a, b, c] + end + + 5.times.map { opt_and_kwargs(1, c: 2) }.uniq +} + +# bmethod with forwarded block +assert_equal '2', %q{ + define_method(:foo) do |&block| + block.call + end + + def bar(&block) + foo(&block) + end + + bar { 1 } + bar { 2 } +} + +# bmethod with forwarded block and arguments +assert_equal '5', %q{ + define_method(:foo) do |n, &block| + n + block.call + end + + def bar(n, &block) + foo(n, &block) + end + + bar(0) { 1 } + bar(3) { 2 } +} + +# bmethod with forwarded unwanted block +assert_equal '1', %q{ + one = 1 + define_method(:foo) do + one + end + + def bar(&block) + foo(&block) + end + + bar { } + bar { } +} + +# test for return stub lifetime issue +assert_equal '1', %q{ + def foo(n) + if n == 2 + return 1.times { Object.define_method(:foo) {} } + end + + foo(n + 1) + end + + foo(1) +} + +# case-when with redefined === +assert_equal 'ok', %q{ + class Symbol + def ===(a) + true + end + end + + def cw(arg) + case arg + when :b + :ok + when 4 + :ng + end + end + + cw(4) +} + +assert_normal_exit %{ + class Bug20997 + def foo(&) = self.class.name(&) + + new.foo + end +} diff --git a/bootstraptest/test_yjit_30k_ifelse.rb b/bootstraptest/test_yjit_30k_ifelse.rb new file mode 100644 index 0000000000..c3afa95e4d --- /dev/null +++ b/bootstraptest/test_yjit_30k_ifelse.rb |
