diff options
5455 files changed, 899109 insertions, 288796 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 @@ -10,15 +10,26 @@ # 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 @@ -34,7 +45,6 @@ README.ja.md COPYING COPYING.ja -CONTRIBUTING.md LEGAL @@ -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) @@ -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 @@ -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/.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_branch.yml b/.github/workflows/check_branch.yml deleted file mode 100644 index 37cf3a9a8f..0000000000 --- a/.github/workflows/check_branch.yml +++ /dev/null @@ -1,22 +0,0 @@ -# We bidirectionally synchronize github.com/ruby/ruby.git's master branch and -# git.ruby-lang.org/ruby.git's master branch. -# We can use a pull request's merge button only on the master branch. -# -# Therefore, we require to pass this "check_branch" on all protected branches -# to prevent us from accidentally pushing commits to GitHub directly. -# -# Details: https://bugs.ruby-lang.org/issues/16094 -name: Pull Request -on: [pull_request] -jobs: - check_branch: - runs-on: ubuntu-latest - steps: - - name: Check if branch is master - run: | - if [ "$BASE_REF" != master ]; then - echo "Only master branch accepts a pull request, but it's '$BASE_REF'." - exit 1 - fi - env: - BASE_REF: ${{ github.base_ref }} 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 index b571409abf..8dba76fbe2 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,39 +1,75 @@ name: "Code scanning - action" on: - push: - pull_request: + # push: + # paths-ignore: + # - 'doc/**' + # - '**/man' + # - '**.md' + # - '**.rdoc' + # - '**/.document' + # pull_request: + # paths-ignore: + # - 'doc/**' + # - '**/man' + # - '**.md' + # - '**.rdoc' + # - '**/.document' schedule: - - cron: '0 12 * * 4' + - cron: '0 12 * * *' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} + cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} + +permissions: # added using https://github.com/step-security/secure-workflows + contents: read jobs: CodeQL-Build: # CodeQL runs on ubuntu-latest and windows-latest + permissions: + actions: read # for github/codeql-action/init to get workflow details + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/autobuild to send a status report runs-on: ubuntu-latest + # 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 libgdbm-dev bison autoconf ruby + sudo apt-get install --no-install-recommends -q -y build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev bison autoconf ruby - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: - fetch-depth: 2 + path: .downloaded-cache + key: downloaded-cache - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} + - 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@v1 + uses: github/codeql-action/init@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2.1.37 with: - languages: cpp + config-file: ./.github/codeql/codeql-config.yml + trap-caching: false + + - name: Set ENV + run: echo "GNUMAKEFLAGS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2.1.37 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2.1.37 diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index 88d6f0e214..caf12cc0f4 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -1,19 +1,42 @@ name: Compilations -on: [push, pull_request] +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' -# Github actions does not support YAML anchors. This creative use of -# environment variables (plus the "echo ::set-env" hack) is to reroute that +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-11 + 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 runs on a relatively modern CPU + # 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 -march=x86-64 -mtune=generic' + optflags: '-O1' # -g0 disables backtraces when SEGV. Do not set that. debugflags: '-ggdb3' @@ -40,162 +63,219 @@ env: --color=always --tty=no +permissions: + contents: read + jobs: compile: strategy: fail-fast: false matrix: + env: + - {} entry: - - { key: default_cc, name: gcc-10, value: gcc-10 } - - { key: default_cc, name: gcc-9, value: gcc-9 } - - { key: default_cc, name: gcc-8, value: gcc-8 } - - { key: default_cc, name: gcc-7, value: gcc-7 } - - { key: default_cc, name: gcc-6, value: gcc-6 } - - { key: default_cc, name: gcc-5, value: gcc-5 } - - { key: default_cc, name: gcc-4.8, value: gcc-4.8 } - - { key: default_cc, name: clang-11, value: clang-11 } - - { key: default_cc, name: clang-10, value: clang-10 } - - { key: default_cc, name: clang-9, value: clang-9 } - - { key: default_cc, name: clang-8, value: clang-8 } - - { key: default_cc, name: clang-7, value: clang-7 } - - { key: default_cc, name: clang-6.0, value: clang-6.0 } - - { key: default_cc, name: clang-5.0, value: clang-5.0 } - - { key: default_cc, name: clang-4.0, value: clang-4.0 } - - { key: default_cc, name: clang-3.9, value: clang-3.9 } - - - { key: append_cc, name: c99, value: '-std=c99 -Werror=pedantic -pedantic-errors' } - - { key: append_cc, name: c11, value: '-std=c11 -Werror=pedantic -pedantic-errors' } - - { key: append_cc, name: c17, value: '-std=c17 -Werror=pedantic -pedantic-errors' } - - { key: append_cc, name: c2x, value: '-std=c2x -Werror=pedantic -pedantic-errors' } - - { key: CXXFLAGS, name: c++98, value: '-std=c++98 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } - - { key: CXXFLAGS, name: c++11, value: '-std=c++11 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } - - { key: CXXFLAGS, name: c++14, value: '-std=c++14 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } - - { key: CXXFLAGS, name: c++17, value: '-std=c++17 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } - - { key: CXXFLAGS, name: c++2a, value: '-std=c++2a -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } - - - { key: optflags, name: '-O0', value: '-O0 -march=x86-64 -mtune=generic' } - - { key: optflags, name: '-O3', value: '-O3 -march=x86-64 -mtune=generic' } - - - { key: append_configure, name: gmp, value: '--with-gmp' } - - { key: append_configure, name: jemalloc, value: '--with-jemalloc' } - - { key: append_configure, name: valgrind, value: '--with-valgrind' } - - { key: append_configure, name: 'coroutine=ucontext', value: '--with-coroutine=ucontext' } - - { key: append_configure, name: 'coroutine=copy', value: '--with-coroutine=copy' } - - { key: append_configure, name: disable-mathn, value: '--disable-mathn' } - - { key: append_configure, name: disable-jit-support, value: '--disable-jit-support' } - - { key: append_configure, name: disable-dln, value: '--disable-dln' } - - { key: append_configure, name: disable-rubygems, value: '--disable-rubygems' } - - - { key: cppflags, name: OPT_THREADED_CODE=1, value: '-DOPT_THREADED_CODE=1' } - - { key: cppflags, name: OPT_THREADED_CODE=2, value: '-DOPT_THREADED_CODE=2' } - - { key: cppflags, name: OPT_THREADED_CODE=3, value: '-DOPT_THREADED_CODE=3' } - - - { key: cppflags, name: NDEBUG, value: '-DNDEBUG' } - - { key: cppflags, name: RUBY_DEBUG, value: '-DRUBY_DEBUG' } - - { key: cppflags, name: ARRAY_DEBUG, value: '-DARRAY_DEBUG' } - - { key: cppflags, name: BIGNUM_DEBUG, value: '-DBIGNUM_DEBUG' } - - { key: cppflags, name: CCAN_LIST_DEBUG, value: '-DCCAN_LIST_DEBUG' } - - { key: cppflags, name: CPDEBUG=-1, value: '-DCPDEBUG=-1' } - - { key: cppflags, name: ENC_DEBUG, value: '-DENC_DEBUG' } - - { key: cppflags, name: GC_DEBUG, value: '-DGC_DEBUG' } - - { key: cppflags, name: HASH_DEBUG, value: '-DHASH_DEBUG' } - - { key: cppflags, name: ID_TABLE_DEBUG, value: '-DID_TABLE_DEBUG' } - - { key: cppflags, name: RGENGC_DEBUG=-1, value: '-DRGENGC_DEBUG=-1' } - - { key: cppflags, name: SYMBOL_DEBUG, value: '-DSYMBOL_DEBUG' } - - { key: cppflags, name: THREAD_DEBUG=-1, value: '-DTHREAD_DEBUG=-1' } - - - { key: cppflags, name: RGENGC_CHECK_MODE, value: '-DRGENGC_CHECK_MODE' } - - { key: cppflags, name: TRANSIENT_HEAP_CHECK_MODE, value: '-DTRANSIENT_HEAP_CHECK_MODE' } - - { key: cppflags, name: VM_CHECK_MODE, value: '-DVM_CHECK_MODE' } - - - { key: cppflags, name: USE_EMBED_CI=0, value: '-DUSE_EMBED_CI=0' } - - { key: cppflags, name: USE_FLONUM=0, value: '-DUSE_FLONUM=0' } -# - { key: cppflags, name: USE_GC_MALLOC_OBJ_INFO_DETAILS, value: '-DUSE_GC_MALLOC_OBJ_INFO_DETAILS' } - - { key: cppflags, name: USE_LAZY_LOAD, value: '-DUSE_LAZY_LOAD' } - - { key: cppflags, name: USE_RINCGC=0, value: '-DUSE_RINCGC=0' } - - { key: cppflags, name: USE_SYMBOL_GC=0, value: '-DUSE_SYMBOL_GC=0' } - - { key: cppflags, name: USE_THREAD_CACHE=0, value: '-DUSE_THREAD_CACHE=0' } - - { key: cppflags, name: USE_TRANSIENT_HEAP=0, value: '-DUSE_TRANSIENT_HEAP=0' } - - - { key: cppflags, name: DEBUG_FIND_TIME_NUMGESS, value: '-DDEBUG_FIND_TIME_NUMGESS' } - - { key: cppflags, name: DEBUG_INTEGER_PACK, value: '-DDEBUG_INTEGER_PACK' } - - { key: cppflags, name: ENABLE_PATH_CHECK, value: '-DENABLE_PATH_CHECK' } - - - { key: cppflags, name: GC_DEBUG_STRESS_TO_CLASS, value: '-DGC_DEBUG_STRESS_TO_CLASS' } - - { key: cppflags, name: GC_ENABLE_LAZY_SWEEP=0, value: '-DGC_ENABLE_LAZY_SWEEP=0' } - - { key: cppflags, name: GC_PROFILE_DETAIL_MEMOTY, value: '-DGC_PROFILE_DETAIL_MEMOTY' } - - { key: cppflags, name: GC_PROFILE_MORE_DETAIL, value: '-DGC_PROFILE_MORE_DETAIL' } - - - { key: cppflags, name: CALC_EXACT_MALLOC_SIZE, value: '-DCALC_EXACT_MALLOC_SIZE' } - - { key: cppflags, name: MALLOC_ALLOCATED_SIZE_CHECK, value: '-DMALLOC_ALLOCATED_SIZE_CHECK' } - - - { key: cppflags, name: IBF_ISEQ_ENABLE_LOCAL_BUFFER, value: '-DIBF_ISEQ_ENABLE_LOCAL_BUFFER' } - - - { key: cppflags, name: RGENGC_ESTIMATE_OLDMALLOC, value: '-DRGENGC_ESTIMATE_OLDMALLOC' } - - { key: cppflags, name: RGENGC_FORCE_MAJOR_GC, value: '-DRGENGC_FORCE_MAJOR_GC' } - - { key: cppflags, name: RGENGC_OBJ_INFO, value: '-DRGENGC_OBJ_INFO' } - - { key: cppflags, name: RGENGC_OLD_NEWOBJ_CHECK, value: '-DRGENGC_OLD_NEWOBJ_CHECK' } - - { key: cppflags, name: RGENGC_PROFILE, value: '-DRGENGC_PROFILE' } - - - { key: cppflags, name: VM_DEBUG_BP_CHECK, value: '-DVM_DEBUG_BP_CHECK' } - - { key: cppflags, name: VM_DEBUG_VERIFY_METHOD_CACHE, value: '-DVM_DEBUG_VERIFY_METHOD_CACHE' } - - - { key: cppflags, name: MJIT_FORCE_ENABLE, value: '-DMJIT_FORCE_ENABLE' } + - { 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: shyouhei/c-compilers:latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" + 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 ::set-env name=${{ matrix.entry.key }}::${{ matrix.entry.value }} - echo ::set-env name=make::make -sj$((1 + $(nproc --all))) - - run: mkdir build - - uses: actions/checkout@v2 + echo "GNUMAKEFLAGS=-sj$((1 + $(nproc --all)))" >> $GITHUB_ENV + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - run: autoconf + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: src/.downloaded-cache + key: downloaded-cache + - run: ./autogen.sh working-directory: src - name: Run configure - working-directory: build - run: | - ../src/configure -C \ - ${default_configure} \ - ${append_configure} \ - --with-gcc="${default_cc} ${append_cc}" - - run: $make incs - working-directory: build - - run: $make - working-directory: build - - run: $make test - working-directory: build - - run: $make install - working-directory: build - if: "matrix.entry.name == '-O3'" - - run: /usr/local/bin/gem install --no-doc timezone tzinfo - working-directory: build - if: "matrix.entry.name == '-O3'" - - run: $make test-tool - working-directory: build - if: "matrix.entry.name == '-O3'" - - run: $make test-all TESTS='-- ruby -ext-' - working-directory: build - if: "matrix.entry.name == '-O3'" - - run: $make test-spec - working-directory: build - if: "matrix.entry.name == '-O3'" - - - uses: k0kubun/action-slack@v2.0.0 + 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": "${{ matrix.entry.name }}", + "env": "${{ github.workflow }} / ${{ matrix.entry.name }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: failure() && github.event_name == 'push' + if: ${{ failure() && github.event_name == 'push' }} + +defaults: + run: + working-directory: build diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index afd0c88aa4..d8dc58b119 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -1,66 +1,113 @@ name: macOS -on: [push, pull_request] +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: - runs-on: macos-latest strategy: matrix: - test_task: [ "check", "test-bundler-parallel", "test-bundled-gems", "leaked-globals" ] + 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}} - if: "!contains(github.event.head_commit.message, '[ci skip]')" + runs-on: ${{ matrix.os }} + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} steps: - - name: Disable Firewall - run: | - sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate off - sudo /usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate + - run: mkdir build + working-directory: - name: git config run: | git config --global advice.detachedHead 0 - - uses: actions/checkout@v2 + git config --global init.defaultBranch garbage + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - run: ./src/tool/actions-commit-info.sh - id: commit_info + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: src/.downloaded-cache + key: downloaded-cache - name: Install libraries run: | - export WAITS='5 60' - tool/travis_retry.sh brew upgrade - tool/travis_retry.sh brew install gdbm gmp libffi openssl@1.1 zlib autoconf automake libtool readline + brew install gmp libffi openssl@1.1 zlib autoconf automake libtool readline bison working-directory: src - name: Set ENV run: | - echo '::set-env name=JOBS::'-j$((1 + $(sysctl -n hw.activecpu))) - - run: autoconf + 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 - - run: mkdir build - name: Run configure run: ../src/configure -C --disable-install-doc --with-openssl-dir=$(brew --prefix openssl@1.1) --with-readline-dir=$(brew --prefix readline) - working-directory: build - - run: make $JOBS incs - working-directory: build - - run: make $JOBS - working-directory: build + - run: make incs - run: make prepare-gems - working-directory: build - if: matrix.test_task == 'check' - - run: make $JOBS -s ${{ matrix.test_task }} - working-directory: build + 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" - # Remove minitest from TEST_BUNDLED_GEMS_ALLOW_FAILURES if https://github.com/seattlerb/minitest/pull/798 is resolved - TEST_BUNDLED_GEMS_ALLOW_FAILURES: "minitest,xmlrpc,rexml" - - uses: k0kubun/action-slack@v2.0.0 + 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": "${{ github.workflow }} / ${{ matrix.test_task }}", + "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 }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: failure() && github.event_name == 'push' + if: ${{ failure() && github.event_name == 'push' }} + +defaults: + run: + working-directory: build diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 95abd3300d..0df917d3d8 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -1,144 +1,179 @@ name: MinGW -on: [push, pull_request] +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-2019 + runs-on: windows-2022 + name: ${{ github.workflow }} (${{ matrix.msystem }}) env: - MSYSTEM: MINGW64 - MSYSTEM_PREFIX: /mingw64 + MSYSTEM: ${{ matrix.msystem }} MSYS2_ARCH: x86_64 CHOST: "x86_64-w64-mingw32" - CFLAGS: "-march=x86-64 -mtune=generic -O3 -pipe -fstack-protector-strong" + CFLAGS: "-march=x86-64 -mtune=generic -O3 -pipe" CXXFLAGS: "-march=x86-64 -mtune=generic -O3 -pipe" CPPFLAGS: "-D_FORTIFY_SOURCE=2 -D__USE_MINGW_ANSI_STDIO=1 -DFD_SETSIZE=2048" - LDFLAGS: "-pipe -fstack-protector-strong" + LDFLAGS: "-pipe" UPDATE_UNICODE: "UNICODE_FILES=. UNICODE_PROPERTY_FILES=. UNICODE_AUXILIARY_FILES=. UNICODE_EMOJI_FILES=." GITPULLOPTIONS: --no-tags origin ${{github.ref}} strategy: matrix: - test_task: [ "check" ] # to make job names consistent + 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, '[ci skip]')" + 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 --system core.autocrlf false - git config --system core.eol lf - git config --system advice.detachedHead 0 - - uses: actions/checkout@v2 + 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 - - run: ./src/tool/actions-commit-info.sh - shell: bash - id: commit_info + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: src/.downloaded-cache + key: downloaded-cache - name: Set up Ruby & MSYS2 - uses: MSP-Greg/setup-ruby-pkgs@v1 + uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 with: - ruby-version: 2.6 - mingw: _upgrade_ gdbm gmp libffi libyaml openssl ragel readline - msys2: automake1.16 bison + ruby-version: ${{ matrix.base_ruby }} + - name: set env + run: | + echo "GNUMAKEFLAGS=-j$((2 * NUMBER_OF_PROCESSORS))" >> $GITHUB_ENV + - name: where check run: | # show where - Write-Host - $where = 'gcc.exe', 'ragel.exe', 'make.exe', 'bison.exe', 'libcrypto-1_1-x64.dll', 'libssl-1_1-x64.dll' - foreach ($e in $where) { - $rslt = where.exe $e 2>&1 | Out-String - if ($rslt.contains($e)) { Write-Host $rslt } - else { Write-Host "`nCan't find $e" } - } - - name: misc setup, autoreconf + 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: | - mkdir build - mkdir install - mkdir temp - cd src - sh -c "autoreconf -fi" + # 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: configure - working-directory: build + - name: autogen run: | - # Actions uses UTF8, causes test failures, similar to normal OS setup - $PSDefaultParameterValues['*:Encoding'] = 'utf8' - [Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("IBM437") - [Console]::InputEncoding = [System.Text.Encoding]::GetEncoding("IBM437") - $config_args = "--build=$env:CHOST --host=$env:CHOST --target=$env:CHOST" - Write-Host $config_args - sh -c "../src/configure --disable-install-doc --prefix=/install $config_args" - # Write-Host "-------------------------------------- config.log" - # Get-Content ./config.log | foreach {Write-Output $_} + ./autogen.sh + working-directory: src + + - name: configure + run: > + ../src/configure --disable-install-doc --prefix=/. + --build=$CHOST --host=$CHOST --target=$CHOST - name: update - working-directory: build run: | - $jobs = [int](2 * $env:NUMBER_OF_PROCESSORS) - make -j $jobs incs + make incs - name: download gems - working-directory: build run: | - $jobs = [int](2 * $env:NUMBER_OF_PROCESSORS) - make -j $jobs update-gems + make update-gems - name: make all - timeout-minutes: 40 - working-directory: build + timeout-minutes: 30 run: | - $jobs = [int](2 * $env:NUMBER_OF_PROCESSORS) - make -j $jobs + make + + - run: make leaked-globals - name: make install - working-directory: build run: | - # Actions uses UTF8, causes test failures, similar to normal OS setup - $PSDefaultParameterValues['*:Encoding'] = 'utf8' - [Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("IBM437") - [Console]::InputEncoding = [System.Text.Encoding]::GetEncoding("IBM437") - make DESTDIR=.. install-nodoc + make DESTDIR=../install install-nodoc - name: test timeout-minutes: 5 - working-directory: build run: | make test + if: ${{matrix.test_task == 'check' || matrix.test_task == 'test'}} - name: test-all - timeout-minutes: 50 - working-directory: build + timeout-minutes: 45 run: | # Actions uses UTF8, causes test failures, similar to normal OS setup - $PSDefaultParameterValues['*:Encoding'] = 'utf8' - [Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("IBM437") - [Console]::InputEncoding = [System.Text.Encoding]::GetEncoding("IBM437") - $jobs = [int](1.5 * $env:NUMBER_OF_PROCESSORS) - make test-all TESTOPTS="-j $jobs --retry --job-status=normal --show-skip --timeout-scale=1.5" + 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 - working-directory: src/spec/ruby run: | - $env:Path = "$pwd/../../../install/bin;$env:Path" - # Actions uses UTF8, causes test failures, similar to normal OS setup - $PSDefaultParameterValues['*:Encoding'] = 'utf8' - [Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("IBM437") - [Console]::InputEncoding = [System.Text.Encoding]::GetEncoding("IBM437") - ruby -v - ruby ../mspec/bin/mspec -j + make ${{ StartsWith(matrix.test_task, 'spec/') && matrix.test_task || 'test-spec' }} + if: ${{matrix.test_task == 'check' || matrix.test_task == 'test-spec' || StartsWith(matrix.test_task, 'spec/')}} - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { "ci": "GitHub Actions", - "env": "${{ github.workflow }} / ${{ matrix.test_task }}", + "env": "${{ github.workflow }} ${{ matrix.msystem }} / ${{ matrix.test_task }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: failure() && github.event_name == 'push' + 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 index 05d944b766..6f7181489a 100644 --- a/.github/workflows/mjit.yml +++ b/.github/workflows/mjit.yml @@ -1,32 +1,69 @@ name: MJIT -on: [push, pull_request] +on: + push: + paths-ignore: + - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + pull_request: + paths-ignore: + - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + - '**.[1-8]' + - '**.ronn' + merge_group: + paths-ignore: + - 'doc/**' + - '**.md' + - '**.rdoc' + - '**/.document' + - '**.[1-8]' + - '**.ronn' + +concurrency: + group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} + cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} + +permissions: + contents: read + jobs: make: strategy: matrix: - test_task: [ "check" ] # to make job names consistent - jit_opts: [ "--jit", "--jit-wait" ] + 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, '[ci skip]')" + 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 --jit-warnings ${{ matrix.jit_opts }}' + 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 libgdbm-dev bison autoconf ruby + 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 - - uses: actions/checkout@v2 + git config --global init.defaultBranch garbage + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - run: ./src/tool/actions-commit-info.sh - id: commit_info + - 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 @@ -34,35 +71,43 @@ jobs: sudo bash -c 'IFS=:; for d in '"$PATH"'; do chmod -v go-w $d; done' || : - name: Set ENV run: | - echo '::set-env name=JOBS::'-j$((1 + $(nproc --all))) - - run: autoconf + echo "GNUMAKEFLAGS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV + - run: ./autogen.sh working-directory: src - - run: mkdir build - name: Run configure - run: ../src/configure -C --disable-install-doc - working-directory: build - - run: make $JOBS incs - working-directory: build - - run: make $JOBS - working-directory: build - - run: sudo make $JOBS -s install - working-directory: build - - run: make $JOBS -s test - working-directory: build - - run: make $JOBS -s test-all - working-directory: build - - run: make $JOBS -s test-spec - working-directory: build - - uses: k0kubun/action-slack@v2.0.0 + 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.jit_opts }}", + "env": "${{ github.workflow }} / ${{ matrix.test_task }} ${{ matrix.mjit_opts }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: failure() && github.event_name == 'push' + 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 index 0d81666e13..4fbca1170e 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -1,88 +1,146 @@ name: Ubuntu -on: [push, pull_request] +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", "test-all TESTS=--repeat-count=2", "leaked-globals" ] - os: [ubuntu-20.04, ubuntu-18.04, ubuntu-16.04] - debug: ["", "-DRUBY_DEBUG"] - exclude: + # 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 - os: ubuntu-16.04 - test_task: test-bundled-gems - os: ubuntu-16.04 - - test_task: "test-all TESTS=--repeat-count=2" - os: ubuntu-16.04 - - test_task: leaked-globals - os: ubuntu-16.04 - - os: ubuntu-16.04 - debug: -DRUBY_DEBUG - - test_task: "test-all TESTS=--repeat-count=2" - debug: -DRUBY_DEBUG - - test_task: leaked-globals - debug: -DRUBY_DEBUG fail-fast: false env: GITPULLOPTIONS: --no-tags origin ${{github.ref}} - runs-on: ${{ matrix.os }} - if: "!contains(github.event.head_commit.message, '[ci skip]')" + 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 build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm-dev bison autoconf ruby + 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 - - uses: actions/checkout@v2 + git config --global init.defaultBranch garbage + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - run: ./src/tool/actions-commit-info.sh - id: commit_info + - 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 '::set-env name=JOBS::'-j$((1 + $(nproc --all))) - - run: autoconf + - run: ./autogen.sh working-directory: src - - run: mkdir build - name: Run configure - run: ../src/configure -C --disable-install-doc cppflags=${{ matrix.debug }} - working-directory: build - - run: make $JOBS incs - working-directory: build - - run: make $JOBS - working-directory: build - - run: make prepare-gems - working-directory: build - if: matrix.test_task == 'check' + 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: | - ./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)")}' - working-directory: build - if: matrix.test_task == 'check' - - run: make $JOBS -s ${{ matrix.test_task }} - working-directory: build + $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" - # Remove minitest from TEST_BUNDLED_GEMS_ALLOW_FAILURES if https://github.com/seattlerb/minitest/pull/798 is resolved - TEST_BUNDLED_GEMS_ALLOW_FAILURES: "minitest,xmlrpc" - - uses: k0kubun/action-slack@v2.0.0 + 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": "${{ matrix.os }} / ${{ matrix.test_task }}${{ matrix.debug }}", + "env": "${{ github.workflow }} / ${{ matrix.test_task }} ${{ matrix.configure }}${{ matrix.arch }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: failure() && github.event_name == 'push' + 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 index 2f1f3a7570..c2bd4881c2 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -1,81 +1,149 @@ name: Windows -on: [push, pull_request] +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: [test] - os: [windows-2019] - vs: [2019] + include: + - vs: 2022 + vcvers: -vcvars_ver=14.2 fail-fast: false - runs-on: ${{ matrix.os }} + 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}} - if: "!contains(github.event.head_commit.message, '[ci skip]')" + PATCH: C:\msys64\usr\bin\patch.exe + OS_VER: windows-${{ matrix.vs }} steps: - - uses: actions/cache@v1 + - run: md build + working-directory: + - uses: msys2/setup-msys2@61f9e5e925871ba6c9e3e8da24ede83ea27fa91f # v2.27.0 + id: setup-msys2 with: - path: C:\vcpkg\downloads - key: ${{ runner.os }}-vcpkg-download-${{ matrix.os }}-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-vcpkg-download-${{ matrix.os }}- - ${{ runner.os }}-vcpkg-download- - - name: Install libraries with vcpkg - run: | - vcpkg --triplet x64-windows install readline zlib - - uses: actions/cache@v1 + 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:\Users\runneradmin\AppData\Local\Temp\chocolatey - key: ${{ runner.os }}-chocolatey-${{ matrix.os }}-${{ github.sha }} + path: C:\vcpkg\installed + key: ${{ runner.os }}-vcpkg-installed-windows-${{ matrix.vs }}-${{ github.sha }} restore-keys: | - ${{ runner.os }}-chocolatey-${{ matrix.os }}- - ${{ runner.os }}-chocolatey- - - name: Install libraries with chocolatey + ${{ runner.os }}-vcpkg-installed-windows-${{ matrix.vs }}- + ${{ runner.os }}-vcpkg-installed-windows- + - name: Install libraries with vcpkg run: | - choco install --no-progress openssl winflexbison3 + 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 --system advice.detachedHead 0 - - uses: actions/checkout@v2 + 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 - - run: ./src/tool/actions-commit-info.sh - shell: bash - id: commit_info - - run: md build - shell: cmd - - name: Configure - run: | - call "C:\Program Files (x86)\Microsoft Visual Studio\${{ matrix.vs }}\Enterprise\VC\Auxiliary\Build\vcvars64.bat" - ../src/win32/configure.bat --disable-install-doc --without-ext=+,dbm,gdbm --enable-bundled-libffi --with-opt-dir=C:/vcpkg/installed/x64-windows --with-openssl-dir="C:/Program Files/OpenSSL-Win64" - working-directory: build - shell: cmd - - name: nmake + - 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: | - call "C:\Program Files (x86)\Microsoft Visual Studio\${{ matrix.vs }}\Enterprise\VC\Auxiliary\Build\vcvars64.bat" - set YACC=win_bison - echo on - nmake incs - nmake extract-extlibs - nmake - working-directory: build - shell: cmd - - name: nmake test + 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: | - call "C:\Program Files (x86)\Microsoft Visual Studio\${{ matrix.vs }}\Enterprise\VC\Auxiliary\Build\vcvars64.bat" - nmake ${{ matrix.test_task }} - working-directory: build - shell: cmd - - uses: k0kubun/action-slack@v2.0.0 + 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": "${{ matrix.os }} / ${{ matrix.test_task }}", + "env": "VS${{ matrix.vs }} / ${{ matrix.test_task || 'check' }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: failure() && github.event_name == 'push' + 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 916c7aaf9e..99d32a1825 100644 --- a/.gitignore +++ b/.gitignore @@ -10,9 +10,11 @@ *.dylib *.elc *.i +*.ii *.inc *.log *.o +*.o.tmp *.obj *.old *.orig @@ -24,10 +26,10 @@ *.sav *.sl *.so +*.so.* *.swp *.yarb *~ -.*-* .*.list .*.time .DS_Store @@ -38,6 +40,7 @@ .ppack .svn .time +.ruby-version Makefile cygruby*.def extconf.h @@ -57,6 +60,8 @@ lcov*.info /*.pc /*.rc /*_prelude.c +/.downloaded-cache +/.top-enc.mk /build*/ /COPYING.LIB /ChangeLog @@ -124,8 +129,10 @@ lcov*.info /ruby-runner /ruby-runner.h /ruby-man.rd.gz +/rubyspec_temp /run.gdb /sizes.c +/static-ruby /test.rb /test-coverage.dat /tmp @@ -140,6 +147,8 @@ lcov*.info /bin/*.exe /bin/*.dll +/bin/goruby +/bin/ruby # /benchmark/ /benchmark/bm_require.data @@ -179,6 +188,9 @@ lcov*.info /ext/-test-/win32/dln/dlntest.exp /ext/-test-/win32/dln/dlntest.lib +# /ext/-test-/gems +/ext/-test-/gems + # /ext/etc/ /ext/etc/constdefs.h @@ -191,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 @@ -210,6 +223,9 @@ lcov*.info /lib/ruby/[1-9]*.* /lib/ruby/vendor_ruby +# /misc/ +/misc/**/__pycache__ + # /spec/bundler /.rspec_status @@ -221,6 +237,14 @@ lcov*.info /win32/*.ico # MJIT -/rb_mjit_header.h -/mjit_config.h /include/ruby-*/*/rb_mjit_min_header-*.h +/lib/ruby_vm/mjit/instruction.rb +/mjit_config.h +/rb_mjit_header.h + +# YJIT +/yjit-bench +/yjit_exit_locations.dump + +# /wasm/ +/wasm/tests/*.wasm diff --git a/.indent.pro b/.indent.pro new file mode 100644 index 0000000000..1d61cbcad1 --- /dev/null +++ b/.indent.pro @@ -0,0 +1,32 @@ +-bap +-nbbb +-nbc +-br +-brs +-nbs +-ncdb +-nce +-cdw +-cli2 +-cbi2 +-ndj +-ncs +-nfc1 +-i4 +-l120 +-lp +-npcs +-psl +-sc +-sob +-sbi4 +-nut +-par + +-TID +-TVALUE +-Tst_data_t +-Tst_index_t +-Tst_table +-Trb_data_type_t +-TFILE diff --git a/.rdoc_options b/.rdoc_options new file mode 100644 index 0000000000..760507c7a2 --- /dev/null +++ b/.rdoc_options @@ -0,0 +1,4 @@ +--- +page_dir: doc +main_page: README.md +title: Documentation for Ruby development version diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 59f50283db..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,541 +0,0 @@ -# -*- YAML -*- -# Copyright (C) 2011 Urabe, Shyouhei. All rights reserved. -# -# This file is a part of the programming language Ruby. Permission is hereby -# granted, to either redistribute or modify this file, provided that the -# conditions mentioned in the file COPYING are met. Consult the file for -# details. - -# 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 - -os: linux - -dist: xenial - -git: - quiet: true - -cache: - ccache: true - directories: - - $HOME/config_2nd - - $HOME/.downloaded-cache - -env: - global: - # Reset timestamps early - - _=$(touch NEWS && find . -type f -exec touch -r NEWS {} +) - - CONFIGURE_TTY=no - - CCACHE_COMPILERCHECK=none - - CCACHE_NOCOMPRESS=1 - - CCACHE_MAXSIZE=512Mi - - NPROC="`nproc`" - # JOBS and SETARCH are overridden when necessary; see below. - - JOBS=-j$((1+${NPROC})) - - SETARCH= - - RUBY_PREFIX=/tmp/ruby-prefix - - GEMS_FOR_TEST='timezone tzinfo' - - UPDATE_UNICODE="UNICODE_FILES=. UNICODE_PROPERTY_FILES=. UNICODE_AUXILIARY_FILES=. UNICODE_EMOJI_FILES=." - - BEFORE_INSTALL=true - # https://github.com/travis-ci/travis-build/blob/e411371dda21430a60f61b8f3f57943d2fe4d344/lib/travis/build/bash/travis_apt_get_options.bash#L7 - - travis_apt_get_options='--allow-downgrades --allow-remove-essential --allow-change-held-packages' - - travis_apt_get_options="-yq --no-install-suggests --no-install-recommends $travis_apt_get_options" - -.org.ruby-lang.ci.matrix-definitions: - - - &cron-only - if: (type = cron) AND (branch = master) AND (fork = false) - - - &make-test-only - script: - - $SETARCH make -s test TESTOPTS="${TESTOPTS=$JOBS -q --tty=no}" - - - &gcc-8 - compiler: gcc-8 - # # Not using addon to control retries - # addons: - # apt: - # sources: - # - ubuntu-toolchain-r-test - before_install: - - bash -cx "${BEFORE_INSTALL}" - - tool/travis_retry.sh sudo -E apt-add-repository -y "ppa:ubuntu-toolchain-r/test" - - tool/travis_retry.sh sudo bash -c "rm -rf '${TRAVIS_ROOT}/var/lib/apt/lists/'* && exec apt-get update -yq" - - >- - tool/travis_retry.sh sudo -E apt-get $travis_apt_get_options install - ccache - gcc-8 - g++-8 - libffi-dev - libgdbm-dev - libgmp-dev - libjemalloc-dev - libncurses5-dev - libncursesw5-dev - libreadline6-dev - libssl-dev - libyaml-dev - openssl - valgrind - zlib1g-dev - - - &clang-8 - compiler: clang-8 - addons: - apt: - # Not doing this manually unlike other sources, because it has been stable. - sources: - - llvm-toolchain-xenial-8 - config: - retries: true - before_install: - - tool/travis_retry.sh sudo bash -c "rm -rf '${TRAVIS_ROOT}/var/lib/apt/lists/'* && exec apt-get update -yq" - - >- - tool/travis_retry.sh sudo -E apt-get $travis_apt_get_options install - clang-8 - llvm-8-tools - libffi-dev - libgdbm-dev - libgmp-dev - libjemalloc-dev - libncurses5-dev - libncursesw5-dev - libreadline6-dev - libssl-dev - libyaml-dev - openssl - valgrind - zlib1g-dev - - # -------- - - - &x86_64-linux - name: x86_64-linux - <<: *gcc-8 - - - &arm64-linux - name: arm64-linux - arch: arm64 - <<: *gcc-8 - - - &s390x-linux - name: s390x-linux - arch: s390x - <<: *gcc-8 - - - &jemalloc - name: --with-jemalloc - <<: *gcc-8 - <<: *cron-only - env: - - CONFIG_FLAG='--with-gmp --with-jemalloc --with-valgrind' - - - &assertions - name: RUBY_DEBUG=1 - <<: *gcc-8 - #<<: *cron-only - <<: *make-test-only - env: - - GEMS_FOR_TEST= - - cppflags='-DRUBY_DEBUG -DVM_CHECK_MODE=1 -DTRANSIENT_HEAP_CHECK_MODE -DRGENGC_CHECK_MODE -DENC_DEBUG' - - - &VM_CHECK_MODE - name: VM_CHECK_MODE=3 - <<: *gcc-8 - <<: *cron-only - <<: *make-test-only - env: - - GEMS_FOR_TEST= - - cppflags=-DVM_CHECK_MODE=0x0003 - - - &SUPPORT_JOKE - name: SUPPORT_JOKE - <<: *gcc-8 - <<: *cron-only - <<: *make-test-only - env: - - BEFORE_INSTALL="sed vm_opts.h -e 's/OPT_SUPPORT_JOKE *0/OPT_SUPPORT_JOKE 1/' -i" - - - &CPDEBUG - name: CPDEBUG - <<: *gcc-8 - <<: *cron-only - <<: *make-test-only - env: - - cppflags=-DCPDEBUG - - - &WITH_COROUTINE_UCONTEXT - name: COROUTINE=ucontext - <<: *gcc-8 - <<: *cron-only - env: - - CONFIG_FLAG='--with-coroutine=ucontext' - - - &WITH_COROUTINE_COPY - name: COROUTINE=copy - <<: *gcc-8 - <<: *cron-only - env: - - CONFIG_FLAG='--with-coroutine=copy' - - - &TOKEN_THREADED_CODE - name: TOKEN_THREADED_CODE - <<: *gcc-8 - <<: *cron-only - <<: *make-test-only - env: - - GEMS_FOR_TEST= - - cppflags=-DOPT_THREADED_CODE=1 - - - &CALL_THREADED_CODE - name: CALL_THREADED_CODE - <<: *gcc-8 - <<: *cron-only - <<: *make-test-only - env: - - GEMS_FOR_TEST= - - cppflags=-DOPT_THREADED_CODE=2 - - - &NO_THREADED_CODE - name: NO_THREADED_CODE - <<: *gcc-8 - <<: *cron-only - <<: *make-test-only - env: - - GEMS_FOR_TEST= - - cppflags=-DOPT_THREADED_CODE=3 - - - &ASAN - name: -fsanitize=address - <<: *clang-8 - #<<: *cron-only - <<: *make-test-only - env: - - GEMS_FOR_TEST= - - ASAN_OPTIONS=detect_leaks=0 - - cflags='-U_FORTIFY_SOURCE -march=native -fsanitize=address -fno-omit-frame-pointer -fPIC' - - debugflags=-ggdb3 - - optflags=-O1 - - LD=clang-8 - - LDFLAGS='-fsanitize=address -fPIC' - - CONFIG_FLAG='--with-out-ext=openssl --without-gmp --without-jemalloc --without-valgrind' - - - &MSAN - name: -fsanitize=memory - <<: *clang-8 - #<<: *cron-only - <<: *make-test-only - env: - - GEMS_FOR_TEST= - - cflags='-U_FORTIFY_SOURCE -fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -fPIC' - - optflags=-O1 - - LD=clang-8 - - LDFLAGS='-fsanitize=memory -fPIC' - - CONFIG_FLAG='--with-out-ext=openssl --without-gmp --without-jemalloc --without-valgrind' - - - &UBSAN - name: -fsanitize=undefined - <<: *clang-8 - #<<: *cron-only - <<: *make-test-only - env: - - GEMS_FOR_TEST= - - cflags='-U_FORTIFY_SOURCE -fsanitize=undefined,integer,nullability -fno-sanitize=implicit-integer-sign-change,unsigned-integer-overflow' - - cppflags=-DUNALIGNED_WORD_ACCESS=0 - - debugflags=-ggdb3 - - optflags='-O1 -march=native' - - LD=clang-8 - - LDFLAGS='-fsanitize=undefined,integer,nullability -fno-sanitize=implicit-integer-sign-change,unsigned-integer-overflow' - - - &i686-linux - name: i686-linux - compiler: gcc-8 - env: - - GCC_FLAGS=-m32 - - CXX='g++-8 -m32' - - debugflags=-g0 - - SETARCH='setarch i686 --verbose --3gb' - # # Not using addon to control retries - # addons: - # apt: - # sources: - # - ubuntu-toolchain-r-test - before_install: - - tool/travis_retry.sh sudo -E apt-add-repository -y "ppa:ubuntu-toolchain-r/test" - - tool/travis_retry.sh sudo bash -c "rm -rf '${TRAVIS_ROOT}/var/lib/apt/lists/'* && exec apt-get update -yq" - - >- - tool/travis_retry.sh sudo -E apt-get $travis_apt_get_options install - gcc-8-multilib - g++-8 - g++-8-multilib - libstdc++-8-dev:i386 - 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 - - - &arm32-linux - name: arm32-linux - arch: arm64 - # https://packages.ubuntu.com/xenial/crossbuild-essential-armhf - compiler: arm-linux-gnueabihf-gcc - env: - - debugflags=-g0 - - SETARCH='setarch linux32 --verbose --32bit' - before_install: - - sudo dpkg --add-architecture armhf - - tool/travis_retry.sh sudo bash -c "rm -rf '${TRAVIS_ROOT}/var/lib/apt/lists/'* && exec apt-get update -yq" - - >- - tool/travis_retry.sh sudo -E apt-get $travis_apt_get_options install - ccache - crossbuild-essential-armhf - libc6:armhf - libstdc++-5-dev:armhf - libffi-dev:armhf - libffi6:armhf - libgdbm-dev:armhf - libgdbm3:armhf - libncurses5-dev:armhf - libncurses5:armhf - libncursesw5-dev:armhf - libreadline6-dev:armhf - libreadline6:armhf - libssl-dev:armhf - libssl1.0.0:armhf - linux-libc-dev:armhf - zlib1g-dev:armhf - zlib1g:armhf - - - &pedanticism - name: -std=c99 -pedantic - compiler: clang - <<: *make-test-only - env: - - GEMS_FOR_TEST= - - GCC_FLAGS='-std=c99 -Werror=pedantic -pedantic-errors' - - CONFIG_FLAG= - - JOBS= - - >- - warnflags=' - -Wall - -Wextra - -Werror=deprecated-declarations - -Werror=division-by-zero - -Werror=extra-tokens - -Werror=implicit-function-declaration - -Werror=implicit-int - -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 - - - &spec-on-old-ruby - language: ruby - before_install: - install: - before_script: chmod -R u+w spec/ruby - # -j randomly hangs. - script: ruby -C spec/ruby ../mspec/bin/mspec . - - - &rubyspec25 - name: Check ruby/spec version guards on Ruby 2.5 - rvm: 2.5.7 - <<: *spec-on-old-ruby - after_failure: - - echo "ruby/spec failed on Ruby 2.5. This is likely because of a missing ruby_version_is guard, please add it. See spec/README.md." - - - &rubyspec27 - name: Check ruby/spec version guards on Ruby 2.7 - rvm: 2.7.0 - <<: *spec-on-old-ruby - after_failure: - - echo "ruby/spec failed on Ruby 2.7. This is likely because of a missing ruby_version_is guard, please add it. See spec/README.md." - - - &baseruby - name: "BASERUBY: Ruby 2.2" - <<: *gcc-8 - <<: *make-test-only - language: ruby - rvm: 2.2 - - - &dependency - name: Check dependencies in makefiles - language: ruby - before_install: - install: - before_script: - - |- - ruby -e 'new = [] - Dir.glob("ext/**/extconf.rb") {|ex| - unless File.exist?(dep = File.dirname(ex)+"/depend") - puts "Adding "+dep - File.copy_stream("template/depend.tmpl", dep) - new << dep - end - } - exec("git", "add", *new) unless new.empty?' - - git diff --cached - - "> config.status" - - "> .rbconfig.time" - - sed -f tool/prereq.status template/Makefile.in common.mk > Makefile - - make touch-unicode-files - - make -s $JOBS $UPDATE_UNICODE -o update-src up - - make -s $JOBS srcs - - rm -f config.status Makefile rbconfig.rb .rbconfig.time - - $SETARCH ./configure -C --disable-install-doc --prefix=$RUBY_PREFIX --disable-rubygems 'optflags=-O0' 'debugflags=-save-temps=obj -g' - - ruby tool/update-deps --fix - script: - - git diff --no-ext-diff --ignore-submodules --exit-code - after_failure: - - echo "Dependencies need to update" - env: - - CONFIG_FLAG= - -matrix: - include: - # Build every commit: - - <<: *x86_64-linux - - <<: *i686-linux - - <<: *pedanticism - - <<: *assertions - - <<: *baseruby - - <<: *rubyspec25 - - <<: *rubyspec27 - - <<: *dependency - # Build every commit (Allowed Failures): - - <<: *arm32-linux - - <<: *arm64-linux - - <<: *s390x-linux - - <<: *ASAN - - <<: *MSAN - - <<: *UBSAN - # Cron only: - - <<: *jemalloc - - <<: *VM_CHECK_MODE - - <<: *SUPPORT_JOKE - - <<: *CPDEBUG - - <<: *WITH_COROUTINE_UCONTEXT - - <<: *WITH_COROUTINE_COPY - - <<: *TOKEN_THREADED_CODE - - <<: *CALL_THREADED_CODE - - <<: *NO_THREADED_CODE - allow_failures: - - name: arm32-linux - - name: arm64-linux - - name: s390x-linux - - name: -fsanitize=address - - name: -fsanitize=memory - fast_finish: true - -before_script: - - rm -fr .ext autom4te.cache - - |- - [ -d ~/.downloaded-cache ] || - mkdir ~/.downloaded-cache - - ln -s ~/.downloaded-cache - - "> config.status" - - "> .rbconfig.time" - - sed -f tool/prereq.status template/Makefile.in common.mk > Makefile - - make touch-unicode-files - - make -s $JOBS $UPDATE_UNICODE up - - make -s $JOBS srcs - - rm -f config.status Makefile rbconfig.rb .rbconfig.time - - |- - if [ -d ~/config_2nd ]; then - cp -pr ~/config_2nd build - else - mkdir build - fi - - mkdir config_1st config_2nd - - chmod -R a-w . - - chmod -R u+w build config_1st config_2nd - - cd build - - |- - case "$CC" in - gcc*) CC="ccache $CC${GCC_FLAGS:+ }$GCC_FLAGS -fno-diagnostics-color";; - clang*) CC="ccache $CC${GCC_FLAGS:+ }$GCC_FLAGS -fno-color-diagnostics";; - esac - - |- - [ ! -f config.cache ] || - [ "$CC" = "`sed -n s/^ac_cv_prog_CC=//p config.cache`" ] || - (set -x; exec rm config.cache) - - $SETARCH ../configure -C --disable-install-doc --prefix=$RUBY_PREFIX $CONFIG_FLAG - - cp -pr config.cache config.status .ext/include ../config_1st - - $SETARCH make reconfig - - cp -pr config.cache config.status .ext/include ../config_2nd - - (cd .. && exec diff -ru config_1st config_2nd) - - chmod u+w .. - - rm -rf ~/config_2nd - - mv ../config_2nd ~ - - chmod u-w .. - - $SETARCH make -s $JOBS - - make -s install - - |- - [ -z "${GEMS_FOR_TEST}" ] || - $RUBY_PREFIX/bin/gem install --no-document $GEMS_FOR_TEST - - echo "raise 'do not load ~/.irbrc in test'" > ~/.irbrc - -script: - - $SETARCH make -s test -o showflags TESTOPTS="${TESTOPTS=$JOBS -q --tty=no}" - - travis_wait 50 $SETARCH make -s test-all -o exts TESTOPTS="${TESTOPTS} ${TEST_ALL_OPTS}" RUBYOPT="-w" - - $SETARCH make -s test-spec MSPECOPT=-ff # not using `-j` because sometimes `mspec -j` silently dies - - $SETARCH make -s -o showflags leaked-globals - -# Branch matrix. Not all branches are Travis-ready so we limit branches here. -branches: - only: - - master - - ruby_2_4 - - ruby_2_5 - - ruby_2_6 - - ruby_2_7 - -# We want to be notified when something happens. -notifications: - irc: - channels: - - "chat.freenode.net#ruby-core" - on_success: change # [always|never|change] # default: always - on_failure: always # [always|never|change] # default: always - template: - - "%{message} by @%{author}: See %{build_url}" - - webhooks: - urls: - - secure: mRsoS/UbqDkKkW5p3AEqM27d4SZnV6Gsylo3bm8T/deltQzTsGzZwrm7OIBXZv0UFZdE68XmPlyHfZFLSP2V9QZ7apXMf9/vw0GtcSe1gchtnjpAPF6lYBn7nMCbVPPx9cS0dwL927fjdRM1vj7IKZ2bk4F0lAJ25R25S6teqdk= # ruby-lang slack: ruby/simpler-alerts-bot (travis) - on_success: never - on_failure: always - - email: - - ko1c-failure@atdot.net 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. @@ -60,6 +60,11 @@ mentioned below. 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] @@ -309,17 +314,6 @@ mentioned below. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. -[aclocal.m4] - - This file is free software. - - >>> - Copyright (C) 1996-2020:: 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. - [tool/config.guess] [tool/config.sub] @@ -349,6 +343,34 @@ mentioned below. program. This Exception is an additional permission under section 7 of the GNU General Public License, version 3 ("GPLv3"). +[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. + [parse.c] [parse.h] @@ -446,7 +468,8 @@ mentioned below. >>> A C-program for MT19937, with initialization improved 2002/2/10.:: - Coded by Takuji Nishimura and Makoto Matsumoto. + 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. @@ -537,12 +560,8 @@ mentioned below. [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] @@ -940,9 +959,7 @@ mentioned below. [lib/bundler] [lib/bundler.rb] -[lib/bundler.gemspec] [spec/bundler] -[man/bundle-*,gemfile.*] Bundler is under the following license. @@ -952,6 +969,73 @@ mentioned below. {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}[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] @@ -1,275 +1,820 @@ -# NEWS for Ruby 2.8.0 (tentative; to be 3.0.0) +# NEWS for Ruby 3.2.0 -This document is a list of user visible feature changes -since the **2.7.0** release, except for bug fixes. +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 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. `https://bugs.ruby-lang.org/issues/$FEATURE_OR_BUG_NUMBER`). +Note that each entry is kept to a minimum, see links for details. ## Language changes -* Keyword arguments are now separated from positional arguments. - Code that resulted in deprecation warnings in Ruby 2.7 will now - result in ArgumentError or different behavior. [[Feature #14183]] - -* Arguments forwarding (`...`) now supports leading arguments. - [[Feature #16378]] +* Anonymous rest and keyword rest arguments can now be passed as + arguments, instead of just used in method parameters. + [[Feature #18351]] ```ruby - def method_missing(meth, ...) - send(:"do_#{meth}", ...) + def foo(*) + bar(*) + end + def baz(**) + quux(**) end ``` -* Procs accepting a single rest argument and keywords are no longer - subject to autosplatting. This now matches the behavior of Procs - accepting a single rest argument and no keywords. - [[Feature #16166]] +* A proc that accepts a single positional argument and keywords will + no longer autosplat. [[Bug #18633]] ```ruby - pr = proc{|*a, **kw| [a, kw]} + proc{|a, **k| a}.call([1, 2]) + # Ruby 3.1 and before + # => 1 + # Ruby 3.2 and after + # => [1, 2] + ``` - pr.call([1]) - # 2.7 => [[1], {}] - # 3.0 => [[[1]], {}] +* Constant assignment evaluation order for constants set on explicit + objects has been made consistent with single attribute assignment + evaluation order. With this code: - pr.call([1, {a: 1}]) - # 2.7 => [[1], {:a=>1}] # and deprecation warning - # 3.0 => [[[1, {:a=>1}]], {}] + ```ruby + foo::BAR = baz ``` -* $SAFE is now a normal global variable with no special behavior. - C-API methods related to $SAFE have been removed. - [[Feature #16131]] + `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: -* yield in singleton class definitions in methods is now a SyntaxError - instead of a warning. yield in a class definition outside of a method - is now a SyntaxError instead of a LocalJumpError. [[Feature #15575]] + 1. `foo1` + 2. `foo2` + 3. `baz1` + 4. `baz2` -* Find pattern is added. [[Feature #16828]] + [[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 - case ["a", 1, "b", "c", 2, "d", "e", "f", 3] - in [*pre, String => x, String => y, *post] - p pre #=> ["a", 1] - p x #=> "b" - p y #=> "c" - p post #=> [2, "d", "e", "f", 3] + def target(**kw) end - ``` -* When a class variable is overtaken by the same definition in an - ancestor class/module, a RuntimeError is now raised (previously, - it only issued a warning in verbose mode. Additionally, accessing a - class variable from the toplevel scope is now a RuntimeError. - [[Bug #14541]] + # 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 -* Rightward assignment statement is added. [EXPERIMENTAL] - [[Feature #15921]] + ruby2_keywords def foo(*args) + bar(*args) + end - ```ruby - fib(10) => x + foo(k: 1) ``` -* Endless method definition is added. [EXPERIMENTAL] - [[Feature #16746]] +## Core classes updates - ```ruby - def square(x) = x * x - ``` +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]] -## Command line options + 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. -### `--help` option + ```ruby + def log(message) + puts "#{Fiber[:request_id]}: #{message}" + end -When the environment variable `RUBY_PAGER` or `PAGER` is present and has -non-empty value, and the standard input and output are tty, `--help` -option shows the help message via the pager designated by the value. -[[Feature #16754]] + def handle_requests + while request = read_request + Fiber.schedule do + Fiber[:request_id] = SecureRandom.uuid -## Core classes updates + request.messages.each do |message| + Fiber.schedule do + log("Handling #{message}") # Log includes inherited request_id. + end + end + end + end + end + ``` -Outstanding ones only. + 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. -* Dir +* Fiber::Scheduler - * Modified method + * Introduce `Fiber::Scheduler#io_select` for non-blocking IO.select. + [[Feature #19060]] - * Dir.glob and Dir.[] now sort the results by default, and - accept `sort:` keyword option. [[Feature #8709]] +* IO -* ENV + * Introduce IO#timeout= and IO#timeout which can cause + IO::TimeoutError to be raised if a blocking operation exceeds the + specified timeout. [[Feature #18630]] - * New method + ```ruby + STDIN.timeout = 1 + STDIN.read # => Blocking operation timed out! (IO::TimeoutError) + ``` - * ENV.except, which returns a hash excluding the given keys - and their values. [[Feature #15822]] + * Introduce `IO.new(..., path:)` and promote `File#path` to `IO#path`. + [[Feature #19036]] -* Hash +* Class - * Modified method + * 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]] - * Hash#transform_keys now accepts a hash that maps keys to new - keys. [[Feature #16274]] + ```ruby + class Foo; end - * New method + 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 + ``` - * Hash#except, which returns a hash excluding the given keys - and their values. [[Feature #15822]] +* Data -* Kernel + * 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]] - * Modified method +* 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 - * Kernel#clone when called with `freeze: false` keyword will call - #initialize_clone with the `freeze: false` keyword. - [[Bug #14266]] + * Integer#ceildiv has been added. [[Feature #18809]] - * Kernel#clone when called with `freeze: true` keyword will call - #initialize_clone with the `freeze: true` keyword, and will - return a frozen copy even if the receiver is unfrozen. - [[Feature #16175]] +* Kernel + + * Kernel#binding raises RuntimeError if called from a non-Ruby frame + (such as a method defined in C). [[Bug #18487]] - * Kernel#eval when called with two arguments will use "(eval)" - for `__FILE__` and 1 for `__LINE__` in the evaluated code. - [[Bug #4352]] +* MatchData - * Kernel#lambda now warns if called without a literal block. - [[Feature #15973]] + * MatchData#byteoffset has been added. [[Feature #13110]] + * MatchData#deconstruct has been added. [[Feature #18821]] + * MatchData#deconstruct_keys has been added. [[Feature #18821]] * Module - * Modified method + * 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]] - * Module#include and #prepend now affect classes and modules that - have already included or prepended the receiver, mirroring the - behavior if the arguments were included in the receiver before - the other modules and classes included or prepended the receiver. - [[Feature #9573]] +* Proc -* Symbol + * Proc#dup returns an instance of subclass. [[Bug #17545]] + * Proc#parameters now accepts lambda keyword. [[Feature #15357]] - * Modified method +* Process + * Added `RLIMIT_NPTS` constant to FreeBSD platform - * Symbol#to_proc now returns a lambda Proc. - [[Feature #16260]] +* Regexp -## Stdlib updates + * 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]] -Outstanding ones only. + * Regexp.linear_time? is introduced. [[Feature #19194]] -* RubyGems + * 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]] - * Update to RubyGems 3.2.0.pre1 + * Regexp.timeout= has been added. Also, Regexp.new new supports timeout keyword. + See [[Feature #17837]] -* Bundler +* 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]] - * Update to Bundler 2.2.0.dev + ```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"> + ``` -* Net::HTTP +* Thread - * New method + * Thread.each_caller_location is added. [[Feature #16663]] - * Add Net::HTTP#verify_hostname= and Net::HTTP#verify_hostname - to skip hostname verification. [[Feature #16555]] +* Thread::Queue - * Modified method + * Thread::Queue#pop(timeout: sec) is added. [[Feature #18774]] - * Net::HTTP.get, Net::HTTP.get_response, and Net::HTTP.get_print can - take request headers as a Hash in the second argument when the first - argument is a URI. +* 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 -Excluding feature bug fixes. +* `String#to_c` currently treat a sequence of underscores as an end of Complex + string. [[Bug #19087]] -* Regexp literals are frozen [[Feature #8948]] [[Feature #16377]] +* Now `ENV.clone` raises `TypeError` as well as `ENV.dup` [[Bug #17767]] - ```ruby - /foo/.frozen? #=> true - ``` +### Removed constants + +The following deprecated constants are removed. -* Bundled gems +* `Fixnum` and `Bignum` [[Feature #12005]] +* `Random::DEFAULT` [[Feature #17351]] +* `Struct::Group` +* `Struct::Passwd` - * net-telnet and xmlrpc have been removed from the bundled gems. - If you are interested in maintaining them, please comment on - your plan to https://github.com/ruby/xmlrpc - or https://github.com/ruby/net-telnet. +### 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]] -* EXPERIMENTAL: Hash#each consistently yields a 2-element array [[Bug #12706]] +### Source code incompatibility of extension libraries - * Now `{ a: 1 }.each(&->(k, v) { })` raises an ArgumentError - due to lambda's arity check. - * This is experimental; if it brings a big incompatibility issue, - it may be reverted until 2.8/3.0 release. +* Extension libraries provide PRNG, subclasses of Random, need updates. + See [PRNG update] below for more information. [[Bug #19100]] -* When writing to STDOUT redirected to a closed pipe, no broken pipe - error message will be shown now. [[Feature #14413]] +### Error printer -* `TRUE`/`FALSE`/`NIL` constants are no longer defined. +* Ruby no longer escapes control characters and backslashes in an + error message. [[Feature #18367]] -* SDBM have been removed from ruby standard library. +### Constant lookup when defining a class/module - * The issues of sdbm will handle at https://github.com/ruby/sdbm +* 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 -Excluding feature bug fixes. +* 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 -* C API functions related to $SAFE have been removed. - [[Feature #16131]] +### 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. -* C API header file `ruby/ruby.h` was split. [[GH-2991]] Should have no impact - on extension libraries, but users might experience slow compilations. +* `rb_cData` variable. +* "taintedness" and "trustedness" functions. [[Feature #16131]] ## Implementation improvements -* The number of hashes allocated when using a keyword splat in - a method call has been reduced to a maximum of 1, and passing - a keyword splat to a method that accepts specific keywords - does not allocate a hash. - -## Miscellaneous changes - -* Methods using `ruby2_keywords` will no longer keep empty keyword - splats, those are now removed just as they are for methods not - using `ruby2_keywords`. - -* Taint deprecation warnings are now issued in regular mode in - addition to verbose warning mode. [[Feature #16131]] - -* When an exception is caught in the default handler, the error - message and backtrace are printed in order from the innermost. - [[Feature #8661]] - - -[Bug #4352]: https://bugs.ruby-lang.org/issues/4352 -[Feature #8661]: https://bugs.ruby-lang.org/issues/8661 -[Feature #8709]: https://bugs.ruby-lang.org/issues/8709 -[Feature #8948]: https://bugs.ruby-lang.org/issues/8948 -[Feature #9573]: https://bugs.ruby-lang.org/issues/9573 -[Feature #14183]: https://bugs.ruby-lang.org/issues/14183 -[Bug #14266]: https://bugs.ruby-lang.org/issues/14266 -[Feature #14413]: https://bugs.ruby-lang.org/issues/14413 -[Feature #15575]: https://bugs.ruby-lang.org/issues/15575 -[Feature #16131]: https://bugs.ruby-lang.org/issues/16131 -[Feature #16166]: https://bugs.ruby-lang.org/issues/16166 -[Feature #16260]: https://bugs.ruby-lang.org/issues/16260 -[Feature #16274]: https://bugs.ruby-lang.org/issues/16274 -[Feature #16377]: https://bugs.ruby-lang.org/issues/16377 -[Bug #12706]: https://bugs.ruby-lang.org/issues/12706 -[Feature #15921]: https://bugs.ruby-lang.org/issues/15921 -[Feature #16555]: https://bugs.ruby-lang.org/issues/16555 -[Feature #16746]: https://bugs.ruby-lang.org/issues/16746 -[Feature #16754]: https://bugs.ruby-lang.org/issues/16754 -[GH-2991]: https://github.com/ruby/ruby/pull/2991 -[Feature #15822]: https://bugs.ruby-lang.org/issues/15822 -[Feature #16378]: https://bugs.ruby-lang.org/issues/16378 -[Feature #16828]: https://bugs.ruby-lang.org/issues/16828 -[Bug #14541]: https://bugs.ruby-lang.org/issues/14541 -[Feature #16175]: https://bugs.ruby-lang.org/issues/16175 -[Feature #15973]: https://bugs.ruby-lang.org/issues/15973 +* 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 bee6433c62..93c0131690 100644 --- a/README.ja.md +++ b/README.ja.md @@ -1,10 +1,9 @@ -[](https://travis-ci.org/ruby/ruby) -[](https://ci.appveyor.com/project/ruby/ruby/branch/master) -[](https://github.com/ruby/ruby/actions?query=workflow%3A"macOS") -[](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://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ã¨ã¯ @@ -52,11 +51,11 @@ Rubyリãƒã‚¸ãƒˆãƒªã®æœ¬æ¥ã®master㯠https://git.ruby-lang.org/ruby.git ã«ã ### Subversion -å¤ã„Rubyã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®ã‚½ãƒ¼ã‚¹ã‚³ãƒ¼ãƒ‰ã¯æ¬¡ã®ã‚³ãƒžãƒ³ãƒ‰ã§å–å¾—ã§ãã¾ã™ï¼Ž +å¤ã„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/ @@ -71,31 +70,26 @@ 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` ãªã©ã‚’生æˆã™ã‚‹ @@ -172,11 +166,14 @@ 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/ ## 著者 @@ -1,12 +1,11 @@ -[](https://travis-ci.org/ruby/ruby) -[](https://ci.appveyor.com/project/ruby/ruby/branch/master) -[](https://github.com/ruby/ruby/actions?query=workflow%3A"macOS") -[](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://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 an interpreted object-oriented programming language often used for web development. It also offers many scripting features @@ -15,28 +14,25 @@ It is simple, straightforward, and extensible. ## Features of Ruby -* Simple Syntax -* **Normal** Object-oriented Features (e.g. class, method calls) -* **Advanced** Object-oriented Features (e.g. mix-in, singleton-method) -* Operator Overloading -* Exception Handling -* Iterators and Closures -* Garbage Collection -* Dynamic Loading of Object Files (on some architectures) -* Highly Portable (works on many Unix-like/POSIX compatible platforms as - well as Windows, macOS, Haiku, etc.) cf. - https://github.com/ruby/ruby/blob/master/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/ -### Git - The mirror of the Ruby source tree can be checked out with the following command: $ git clone https://github.com/ruby/ruby.git @@ -49,21 +45,19 @@ to see the list of branches: You may also want to use https://git.ruby-lang.org/ruby.git (actual master of Ruby source) if you are a committer. -### Subversion - -Stable branches for older Ruby versions can be checked out with the following command: - - $ svn co https://svn.ruby-lang.org/repos/ruby/branches/ruby_2_6/ ruby - -Try the following command to see the list of branches: - - $ svn ls https://svn.ruby-lang.org/repos/ruby/branches/ +## How to build +see [Building Ruby](doc/contributing/building_ruby.md) ## Ruby home page https://www.ruby-lang.org/ +## Documentation + +- [English](https://docs.ruby-lang.org/en/master/index.html) +- [Japanese](https://docs.ruby-lang.org/ja/master/index.html) + ## Mailing list There is a mailing list to discuss Ruby. To subscribe to this list, please @@ -71,98 +65,24 @@ send the following phrase: subscribe -in the mail body (not subject) to the address -[ruby-talk-request@ruby-lang.org](mailto:ruby-talk-request@ruby-lang.org?subject=Join%20Ruby%20Mailing%20List&body=subscribe). - -## How to compile and install - -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 `include/ruby/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. +in the mail body (not subject) to the address [ruby-talk-request@ruby-lang.org]. - 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`. - - Usually this step will not be needed. - -6. Run `make`. - - * On Mac, set RUBY\_CODESIGN environment variable with a signing identity. - It uses the identity to sign `ruby` binary. See also codesign(1). - -7. Optionally, run '`make check`' to check whether the compiled Ruby - interpreter works well. If you see the message "`check succeeded`", your - Ruby works as it should (hopefully). - -8. Run '`make install`'. - - This command will create the following directories and install files into - them. - - * `${DESTDIR}${prefix}/bin` - * `${DESTDIR}${prefix}/include/ruby-${MAJOR}.${MINOR}.${TEENY}` - * `${DESTDIR}${prefix}/include/ruby-${MAJOR}.${MINOR}.${TEENY}/${PLATFORM}` - * `${DESTDIR}${prefix}/lib` - * `${DESTDIR}${prefix}/lib/ruby` - * `${DESTDIR}${prefix}/lib/ruby/${MAJOR}.${MINOR}.${TEENY}` - * `${DESTDIR}${prefix}/lib/ruby/${MAJOR}.${MINOR}.${TEENY}/${PLATFORM}` - * `${DESTDIR}${prefix}/lib/ruby/site_ruby` - * `${DESTDIR}${prefix}/lib/ruby/site_ruby/${MAJOR}.${MINOR}.${TEENY}` - * `${DESTDIR}${prefix}/lib/ruby/site_ruby/${MAJOR}.${MINOR}.${TEENY}/${PLATFORM}` - * `${DESTDIR}${prefix}/lib/ruby/vendor_ruby` - * `${DESTDIR}${prefix}/lib/ruby/vendor_ruby/${MAJOR}.${MINOR}.${TEENY}` - * `${DESTDIR}${prefix}/lib/ruby/vendor_ruby/${MAJOR}.${MINOR}.${TEENY}/${PLATFORM}` - * `${DESTDIR}${prefix}/lib/ruby/gems/${MAJOR}.${MINOR}.${TEENY}` - * `${DESTDIR}${prefix}/share/man/man1` - * `${DESTDIR}${prefix}/share/ri/${MAJOR}.${MINOR}.${TEENY}/system` - - - If Ruby's API version is '*x.y.z*', the `${MAJOR}` is '*x*', the - `${MINOR}` is '*y*', and the `${TEENY}` is '*z*'. - - **NOTE**: teeny of the API version may be different from one of Ruby's - program version - - You may have to be a super user to install Ruby. - -If you fail to compile Ruby, please send the detailed error report with the -error log and machine/OS type, to help others. - -Some extension libraries may not get compiled because of lack of necessary -external libraries and/or headers, then you will need to run '`make distclean-ext`' -to remove old configuration after installing them in such case. +[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). - -Bugs should be reported at https://bugs.ruby-lang.org. Read [HowToReport] for more information. +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. -[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 diff --git a/aclocal.m4 b/aclocal.m4 index 940d91e83f..e69de29bb2 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -1,48 +0,0 @@ -# generated automatically by aclocal 1.16.2 -*- Autoconf -*- - -# Copyright (C) 1996-2020 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_funcs.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_cxxflags.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 bf6bc6dc24..e5f25293e2 100644 --- a/addr2line.c +++ b/addr2line.c @@ -159,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) @@ -173,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(); @@ -190,12 +198,12 @@ struct debug_section_definition { 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; @@ -207,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; @@ -229,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 ""; @@ -246,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; @@ -288,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; } } @@ -313,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; @@ -330,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; @@ -351,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; @@ -372,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; @@ -390,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) @@ -411,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; @@ -437,7 +472,8 @@ parse_debug_line_cu(int num_traces, void **traces, char **debug_line, 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: @@ -447,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: @@ -474,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", @@ -497,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; @@ -526,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; @@ -559,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 @@ -764,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; + 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 { @@ -800,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; @@ -811,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) @@ -833,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); @@ -873,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); @@ -914,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; @@ -930,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); } } @@ -946,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; @@ -957,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; @@ -971,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; @@ -979,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; @@ -987,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; @@ -1003,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); @@ -1067,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: @@ -1115,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)); @@ -1134,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)); @@ -1153,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; @@ -1188,10 +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) { - char *p; + const char *p; if (abbrev_number < ABBREV_TABLE_SIZE) { return reader->abbrev_table[abbrev_number]; } @@ -1199,12 +1350,7 @@ 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; - } + 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); @@ -1212,12 +1358,7 @@ di_find_abbrev(DebugInfoReader *reader, uint64_t 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); } return p; } @@ -1231,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]); @@ -1252,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; } @@ -1312,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; @@ -1322,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) { @@ -1358,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); @@ -1369,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; } } } @@ -1427,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; } @@ -1459,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 */ @@ -1505,6 +1843,13 @@ 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) { + + 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 = {}; @@ -1531,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; @@ -1539,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; @@ -1547,7 +1892,7 @@ 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; */ } } @@ -1556,9 +1901,9 @@ debug_info_read(DebugInfoReader *reader, int num_traces, void **traces, for (int i=offset; i < num_traces; i++) { uintptr_t addr = (uintptr_t)traces[i]; uintptr_t offset = addr - reader->obj->base_addr + reader->obj->vmaddr; - uintptr_t saddr = ranges_include(reader, &ranges, offset); + uintptr_t saddr = ranges_include(reader, &ranges, offset, &rnglists_header); if (saddr) { - /* fprintf(stderr, "%d:%tx: %d %lx->%lx %x %s: %s/%s %d %s %s %s\n",__LINE__,die.pos, i,addr,offset, die.tag,line.sname,line.dirname,line.filename,line.line,reader->obj->path,line.sname,lines[i].sname); */ + /* fprintf(stdout, "%d:%tx: %d %lx->%lx %x %s: %s/%s %d %s %s %s\n",__LINE__,die.pos, i,addr,offset, die.tag,line.sname,line.dirname,line.filename,line.line,reader->obj->path,line.sname,lines[i].sname); */ if (lines[i].sname) { line_info_t *lp = malloc(sizeof(line_info_t)); memcpy(lp, &lines[i], sizeof(line_info_t)); @@ -1577,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; @@ -1601,6 +1995,7 @@ uncompress_debug_section(ElfW(Shdr) *shdr, char *file, char **ptr) fail: free(*ptr); + *ptr = NULL; #endif return 0; } @@ -1615,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; @@ -1687,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; @@ -1697,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++) { @@ -1802,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; } @@ -1946,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) { @@ -1983,7 +2399,7 @@ found_mach_header: char *strtab = file + cmd->stroff, *sname = 0; uint32_t j; uintptr_t saddr = 0; - /* kprintf("[%2d]: %x/symtab %p\n", i, cmd->cmd, p); */ + /* 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]; @@ -2035,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) @@ -2044,17 +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) { @@ -2098,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); @@ -2137,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; @@ -2167,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; } } @@ -2178,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/appveyor.yml b/appveyor.yml deleted file mode 100644 index 75452e29ea..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,95 +0,0 @@ ---- -version: '{build}' -init: - - git config --global user.name git - - git config --global user.email svn-admin@ruby-lang.org -clone_depth: 10 -platform: - - x64 -environment: - ruby_version: "24-%Platform%" - zlib_version: "1.2.11" - matrix: - - build: vs - vs: 120 - ssl: OpenSSL - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - GEMS_FOR_TEST: "" - - build: vs - vs: 140 - ssl: OpenSSL-v111 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - GEMS_FOR_TEST: "" - RELINE_TEST_ENCODING: "Windows-31J" - UPDATE_UNICODE: "UNICODE_FILES=. UNICODE_PROPERTY_FILES=. UNICODE_AUXILIARY_FILES=. UNICODE_EMOJI_FILES=." -for: -- - matrix: - only: - - build: vs - install: - - ver - - chcp - - SET BITS=%Platform:x86=32% - - SET BITS=%BITS:x=% - - SET OPENSSL_DIR=C:\%ssl%-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% %UPDATE_UNICODE% incs - - nmake %mflags% extract-extlibs - - del Makefile - - mkdir \usr\local\bin - - mkdir \usr\local\include - - mkdir \usr\local\lib - - curl -fsSL -o zlib%zlib_version:.=%.zip --retry 10 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 - - attrib +r /s /d - - mkdir %Platform%-mswin_%vs% - 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% - - \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=-v --timeout-scale=3.0 --excludes=../test/excludes/_appveyor -j%JOBS% --exclude readline --exclude win32ole --exclude test_bignum --exclude test_syntax --exclude test_open-uri --exclude test_bundled_ca" test-all - # separately execute tests without -j which may crash worker with -j. - - nmake -l "TESTOPTS=-v --timeout-scale=3.0 --excludes=../test/excludes/_appveyor" test-all TESTS="../test/win32ole ../test/ruby/test_bignum.rb ../test/ruby/test_syntax.rb ../test/open-uri/test_open-uri.rb ../test/rubygems/test_bundled_ca.rb" - - nmake -l 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 / 2015", - "url": "{{buildUrl}}", - "commit": "{{commitId}}", - "branch": "{{branch}}" - } - {{/isPullRequest}} - on_build_success: false - on_build_failure: true - on_build_status_changed: false @@ -39,6 +39,37 @@ VALUE rb_cArray; +/* Flags of RArray + * + * 1: RARRAY_EMBED_FLAG + * The array is embedded (its contents follow the header, rather than + * being on a separately allocated buffer). + * 2: RARRAY_SHARED_FLAG (equal to ELTS_SHARED) + * The array is shared. The buffer this array points to is owned by + * another array (the shared root). + * if USE_RVARGC + * 3-9: RARRAY_EMBED_LEN + * The length of the array when RARRAY_EMBED_FLAG is set. + * else + * 3-4: RARRAY_EMBED_LEN + * The length of the array when RARRAY_EMBED_FLAG is set. + * endif + * 12: RARRAY_SHARED_ROOT_FLAG + * The array is a shared root that does reference counting. The buffer + * this array points to is owned by this array but may be pointed to + * by other arrays. + * Note: Frozen arrays may be a shared root without this flag being + * set. Frozen arrays do not have reference counting because + * they cannot be modified. Not updating the reference count + * improves copy-on-write performance. Their reference count is + * assumed to be infinity. + * 13: RARRAY_TRANSIENT_FLAG + * The buffer of the array is allocated on the transient heap. + * 14: RARRAY_PTR_IN_USE_FLAG + * The buffer of the array is in use. This is only used during + * debugging. + */ + /* for OPTIMIZED_CMP: */ #define id_cmp idCmp @@ -46,28 +77,13 @@ VALUE rb_cArray; #define ARY_MAX_SIZE (LONG_MAX / (int)sizeof(VALUE)) #define SMALL_ARRAY_LEN 16 +RBIMPL_ATTR_MAYBE_UNUSED() static int should_be_T_ARRAY(VALUE ary) { return RB_TYPE_P(ary, T_ARRAY); } -static int -should_not_be_shared_and_embedded(VALUE ary) -{ - return !FL_TEST((ary), ELTS_SHARED) || !FL_TEST((ary), RARRAY_EMBED_FLAG); -} - -#define ARY_SHARED_P(ary) \ - (assert(should_be_T_ARRAY((VALUE)(ary))), \ - assert(should_not_be_shared_and_embedded((VALUE)ary)), \ - FL_TEST_RAW((ary),ELTS_SHARED)!=0) - -#define ARY_EMBED_P(ary) \ - (assert(should_be_T_ARRAY((VALUE)(ary))), \ - assert(should_not_be_shared_and_embedded((VALUE)ary)), \ - FL_TEST_RAW((ary), RARRAY_EMBED_FLAG) != 0) - #define ARY_HEAP_PTR(a) (assert(!ARY_EMBED_P(a)), RARRAY(a)->as.heap.ptr) #define ARY_HEAP_LEN(a) (assert(!ARY_EMBED_P(a)), RARRAY(a)->as.heap.len) #define ARY_HEAP_CAPA(a) (assert(!ARY_EMBED_P(a)), assert(!ARY_SHARED_ROOT_P(a)), \ @@ -77,11 +93,11 @@ should_not_be_shared_and_embedded(VALUE ary) #define ARY_EMBED_LEN(a) \ (assert(ARY_EMBED_P(a)), \ (long)((RBASIC(a)->flags >> RARRAY_EMBED_LEN_SHIFT) & \ - (RARRAY_EMBED_LEN_MASK >> RARRAY_EMBED_LEN_SHIFT))) + (RARRAY_EMBED_LEN_MASK >> RARRAY_EMBED_LEN_SHIFT))) #define ARY_HEAP_SIZE(a) (assert(!ARY_EMBED_P(a)), assert(ARY_OWNS_HEAP_P(a)), ARY_CAPA(a) * sizeof(VALUE)) #define ARY_OWNS_HEAP_P(a) (assert(should_be_T_ARRAY((VALUE)(a))), \ - !FL_TEST_RAW((a), ELTS_SHARED|RARRAY_EMBED_FLAG)) + !FL_TEST_RAW((a), RARRAY_SHARED_FLAG|RARRAY_EMBED_FLAG)) #define FL_SET_EMBED(a) do { \ assert(!ARY_SHARED_P(a)); \ @@ -93,9 +109,9 @@ should_not_be_shared_and_embedded(VALUE ary) #define FL_UNSET_EMBED(ary) FL_UNSET((ary), RARRAY_EMBED_FLAG|RARRAY_EMBED_LEN_MASK) #define FL_SET_SHARED(ary) do { \ assert(!ARY_EMBED_P(ary)); \ - FL_SET((ary), ELTS_SHARED); \ + FL_SET((ary), RARRAY_SHARED_FLAG); \ } while (0) -#define FL_UNSET_SHARED(ary) FL_UNSET((ary), ELTS_SHARED) +#define FL_UNSET_SHARED(ary) FL_UNSET((ary), RARRAY_SHARED_FLAG) #define ARY_SET_PTR(ary, p) do { \ assert(!ARY_EMBED_P(ary)); \ @@ -105,7 +121,6 @@ should_not_be_shared_and_embedded(VALUE ary) #define ARY_SET_EMBED_LEN(ary, n) do { \ long tmp_n = (n); \ assert(ARY_EMBED_P(ary)); \ - assert(!OBJ_FROZEN(ary)); \ RBASIC(ary)->flags &= ~RARRAY_EMBED_LEN_MASK; \ RBASIC(ary)->flags |= (tmp_n) << RARRAY_EMBED_LEN_SHIFT; \ } while (0) @@ -137,7 +152,7 @@ should_not_be_shared_and_embedded(VALUE ary) } \ } while (0) -#define ARY_CAPA(ary) (ARY_EMBED_P(ary) ? RARRAY_EMBED_LEN_MAX : \ +#define ARY_CAPA(ary) (ARY_EMBED_P(ary) ? ary_embed_capa(ary) : \ ARY_SHARED_ROOT_P(ary) ? RARRAY_LEN(ary) : ARY_HEAP_CAPA(ary)) #define ARY_SET_CAPA(ary, n) do { \ assert(!ARY_EMBED_P(ary)); \ @@ -146,26 +161,25 @@ should_not_be_shared_and_embedded(VALUE ary) RARRAY(ary)->as.heap.aux.capa = (n); \ } while (0) -#define ARY_SHARED_ROOT(ary) (assert(ARY_SHARED_P(ary)), RARRAY(ary)->as.heap.aux.shared_root) #define ARY_SET_SHARED(ary, value) do { \ const VALUE _ary_ = (ary); \ const VALUE _value_ = (value); \ assert(!ARY_EMBED_P(_ary_)); \ assert(ARY_SHARED_P(_ary_)); \ - assert(ARY_SHARED_ROOT_P(_value_)); \ + assert(!OBJ_FROZEN(_ary_)); \ + assert(ARY_SHARED_ROOT_P(_value_) || OBJ_FROZEN(_value_)); \ RB_OBJ_WRITE(_ary_, &RARRAY(_ary_)->as.heap.aux.shared_root, _value_); \ } while (0) -#define RARRAY_SHARED_ROOT_FLAG FL_USER5 -#define ARY_SHARED_ROOT_P(ary) (assert(should_be_T_ARRAY((VALUE)(ary))), \ - FL_TEST_RAW((ary), RARRAY_SHARED_ROOT_FLAG)) -#define ARY_SHARED_ROOT_REFCNT(ary) \ - (assert(ARY_SHARED_ROOT_P(ary)), RARRAY(ary)->as.heap.aux.capa) -#define ARY_SHARED_ROOT_OCCUPIED(ary) (ARY_SHARED_ROOT_REFCNT(ary) == 1) + +#define ARY_SHARED_ROOT_OCCUPIED(ary) (!OBJ_FROZEN(ary) && ARY_SHARED_ROOT_REFCNT(ary) == 1) #define ARY_SET_SHARED_ROOT_REFCNT(ary, value) do { \ assert(ARY_SHARED_ROOT_P(ary)); \ + assert(!OBJ_FROZEN(ary)); \ + assert((value) >= 0); \ RARRAY(ary)->as.heap.aux.capa = (value); \ } while (0) #define FL_SET_SHARED_ROOT(ary) do { \ + assert(!OBJ_FROZEN(ary)); \ assert(!ARY_EMBED_P(ary)); \ assert(!RARRAY_TRANSIENT_P(ary)); \ FL_SET((ary), RARRAY_SHARED_ROOT_FLAG); \ @@ -181,6 +195,65 @@ ARY_SET(VALUE a, long i, VALUE v) } #undef RARRAY_ASET +static long +ary_embed_capa(VALUE ary) +{ +#if USE_RVARGC + size_t size = rb_gc_obj_slot_size(ary) - offsetof(struct RArray, as.ary); + assert(size % sizeof(VALUE) == 0); + return size / sizeof(VALUE); +#else + return RARRAY_EMBED_LEN_MAX; +#endif +} + +static size_t +ary_embed_size(long capa) +{ + return offsetof(struct RArray, as.ary) + (sizeof(VALUE) * capa); +} + +static bool +ary_embeddable_p(long capa) +{ +#if USE_RVARGC + return rb_gc_size_allocatable_p(ary_embed_size(capa)); +#else + return capa <= RARRAY_EMBED_LEN_MAX; +#endif +} + +bool +rb_ary_embeddable_p(VALUE ary) +{ + /* An array cannot be turned embeddable when the array is: + * - Shared root: other objects may point to the buffer of this array + * so we cannot make it embedded. + * - Frozen: this array may also be a shared root without the shared root + * flag. + * - Shared: we don't want to re-embed an array that points to a shared + * root (to save memory). + */ + return !(ARY_SHARED_ROOT_P(ary) || OBJ_FROZEN(ary) || ARY_SHARED_P(ary)); +} + +size_t +rb_ary_size_as_embedded(VALUE ary) +{ + size_t real_size; + + if (ARY_EMBED_P(ary)) { + real_size = ary_embed_size(ARY_EMBED_LEN(ary)); + } + else if (rb_ary_embeddable_p(ary)) { + real_size = ary_embed_size(ARY_HEAP_CAPA(ary)); + } + else { + real_size = sizeof(struct RArray); + } + return real_size; +} + #if ARRAY_DEBUG #define ary_verify(ary) ary_verify_(ary, __FILE__, __LINE__) @@ -190,19 +263,19 @@ ary_verify_(VALUE ary, const char *file, int line) { assert(RB_TYPE_P(ary, T_ARRAY)); - if (FL_TEST(ary, ELTS_SHARED)) { - VALUE root = RARRAY(ary)->as.heap.aux.shared_root; + if (ARY_SHARED_P(ary)) { + VALUE root = ARY_SHARED_ROOT(ary); const VALUE *ptr = ARY_HEAP_PTR(ary); const VALUE *root_ptr = RARRAY_CONST_PTR_TRANSIENT(root); long len = ARY_HEAP_LEN(ary), root_len = RARRAY_LEN(root); - assert(FL_TEST(root, RARRAY_SHARED_ROOT_FLAG)); + assert(ARY_SHARED_ROOT_P(root) || OBJ_FROZEN(root)); assert(root_ptr <= ptr && ptr + len <= root_ptr + root_len); ary_verify(root); } else if (ARY_EMBED_P(ary)) { assert(!RARRAY_TRANSIENT_P(ary)); assert(!ARY_SHARED_P(ary)); - assert(RARRAY_LEN(ary) <= RARRAY_EMBED_LEN_MAX); + assert(RARRAY_LEN(ary) <= ary_embed_capa(ary)); } else { #if 1 @@ -258,7 +331,7 @@ void rb_mem_clear(VALUE *mem, long size) { while (size--) { - *mem++ = Qnil; + *mem++ = Qnil; } } @@ -266,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); }); } @@ -274,7 +347,7 @@ static inline void memfill(register VALUE *mem, register long size, register VALUE val) { while (size--) { - *mem++ = val; + *mem++ = val; } } @@ -282,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); }); } @@ -352,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); @@ -377,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 @@ -384,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)); @@ -433,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) { @@ -440,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); @@ -451,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)) { @@ -491,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); @@ -505,39 +601,40 @@ ary_double_capa(VALUE ary, long min) static void rb_ary_decrement_share(VALUE shared_root) { - if (shared_root) { - long num = ARY_SHARED_ROOT_REFCNT(shared_root) - 1; - if (num == 0) { - rb_ary_free(shared_root); - rb_gc_force_recycle(shared_root); - } - else if (num > 0) { - ARY_SET_SHARED_ROOT_REFCNT(shared_root, num); - } + if (!OBJ_FROZEN(shared_root)) { + long num = ARY_SHARED_ROOT_REFCNT(shared_root); + ARY_SET_SHARED_ROOT_REFCNT(shared_root, num - 1); } } static void rb_ary_unshare(VALUE ary) { - VALUE shared_root = RARRAY(ary)->as.heap.aux.shared_root; + VALUE shared_root = ARY_SHARED_ROOT(ary); rb_ary_decrement_share(shared_root); FL_UNSET_SHARED(ary); } -static inline void -rb_ary_unshare_safe(VALUE ary) +static void +rb_ary_reset(VALUE ary) { - if (ARY_SHARED_P(ary) && !ARY_EMBED_P(ary)) { - rb_ary_unshare(ary); + if (ARY_OWNS_HEAP_P(ary)) { + ary_heap_free(ary); + } + else if (ARY_SHARED_P(ary)) { + rb_ary_unshare(ary); } + + FL_SET_EMBED(ary); + ARY_SET_EMBED_LEN(ary, 0); } static VALUE rb_ary_increment_share(VALUE shared_root) { - long num = ARY_SHARED_ROOT_REFCNT(shared_root); - if (num >= 0) { + if (!OBJ_FROZEN(shared_root)) { + long num = ARY_SHARED_ROOT_REFCNT(shared_root); + assert(num >= 0); ARY_SET_SHARED_ROOT_REFCNT(shared_root, num + 1); } return shared_root; @@ -560,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); + long shared_len, len = RARRAY_LEN(ary); VALUE shared_root = ARY_SHARED_ROOT(ary); 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); + 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_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); + FL_UNSET_SHARED(ary); ARY_SET_PTR(ary, RARRAY_CONST_PTR_TRANSIENT(shared_root)); - ARY_SET_CAPA(ary, shared_len); + ARY_SET_CAPA(ary, shared_len); RARRAY_PTR_USE_TRANSIENT(ary, ptr, { - MEMMOVE(ptr, ptr+shift, VALUE, len); - }); + 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); @@ -596,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) { @@ -609,40 +712,40 @@ ary_ensure_room_for_push(VALUE ary, long add_len) long capa; if (old_len > ARY_MAX_SIZE - add_len) { - rb_raise(rb_eIndexError, "index %ld too big", new_len); + rb_raise(rb_eIndexError, "index %ld too big", new_len); } if (ARY_SHARED_P(ary)) { - if (new_len > RARRAY_EMBED_LEN_MAX) { + if (new_len > ary_embed_capa(ary)) { VALUE shared_root = ARY_SHARED_ROOT(ary); if (ARY_SHARED_ROOT_OCCUPIED(shared_root)) { if (ARY_HEAP_PTR(ary) - RARRAY_CONST_PTR_TRANSIENT(shared_root) + new_len <= RARRAY_LEN(shared_root)) { - rb_ary_modify_check(ary); + rb_ary_modify_check(ary); ary_verify(ary); ary_verify(shared_root); return shared_root; - } - else { - /* if array is shared, then it is likely it participate in push/shift pattern */ - rb_ary_modify(ary); - capa = ARY_CAPA(ary); - if (new_len > capa - (capa >> 6)) { - ary_double_capa(ary, new_len); - } + } + else { + /* if array is shared, then it is likely it participate in push/shift pattern */ + rb_ary_modify(ary); + capa = ARY_CAPA(ary); + if (new_len > capa - (capa >> 6)) { + ary_double_capa(ary, new_len); + } ary_verify(ary); - return ary; - } - } - } + return ary; + } + } + } ary_verify(ary); rb_ary_modify(ary); } else { - rb_ary_modify_check(ary); + rb_ary_modify_check(ary); } capa = ARY_CAPA(ary); if (new_len > capa) { - ary_double_capa(ary, new_len); + ary_double_capa(ary, new_len); } ary_verify(ary); @@ -654,15 +757,13 @@ ary_ensure_room_for_push(VALUE ary, long add_len) * array.freeze -> self * * Freezes +self+; returns +self+: + * * a = [] * a.frozen? # => false - * a1 = a.freeze # => [] + * a.freeze * a.frozen? # => true - * a1.equal?(a) # => true # Returned self * - * An attempt to modify a frozen \Array raises an exception: - * # Raises FrozenError (can't modify frozen Array: [:foo, "bar", 2]): - * [:foo, 'bar', 2].freeze.push(:foo) + * An attempt to modify a frozen \Array raises FrozenError. */ VALUE @@ -682,18 +783,25 @@ VALUE rb_ary_shared_with_p(VALUE ary1, VALUE ary2) { if (!ARY_EMBED_P(ary1) && ARY_SHARED_P(ary1) && - !ARY_EMBED_P(ary2) && ARY_SHARED_P(ary2) && - RARRAY(ary1)->as.heap.aux.shared_root == RARRAY(ary2)->as.heap.aux.shared_root && - RARRAY(ary1)->as.heap.len == RARRAY(ary2)->as.heap.len) { - return Qtrue; + !ARY_EMBED_P(ary2) && ARY_SHARED_P(ary2) && + ARY_SHARED_ROOT(ary1) == ARY_SHARED_ROOT(ary2) && + ARY_HEAP_LEN(ary1) == ARY_HEAP_LEN(ary2)) { + return Qtrue; } return Qfalse; } static VALUE -ary_alloc(VALUE klass) +ary_alloc_embed(VALUE klass, long capa) { - NEWOBJ_OF(ary, struct RArray, klass, T_ARRAY | RARRAY_EMBED_FLAG | (RGENGC_WB_PROTECTED_ARRAY ? FL_WB_PROTECTED : 0)); + size_t size = ary_embed_size(capa); + assert(rb_gc_size_allocatable_p(size)); +#if !USE_RVARGC + assert(size <= sizeof(struct RArray)); +#endif + RVARGC_NEWOBJ_OF(ary, struct RArray, klass, + T_ARRAY | RARRAY_EMBED_FLAG | (RGENGC_WB_PROTECTED_ARRAY ? FL_WB_PROTECTED : 0), + size); /* Created array is: * FL_SET_EMBED((VALUE)ary); * ARY_SET_EMBED_LEN((VALUE)ary, 0); @@ -702,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 @@ -714,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); } @@ -743,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 @@ -757,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); @@ -772,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; @@ -785,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_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_tmp_new(long capa) +rb_ary_hidden_new(long capa) { VALUE ary = ary_new(0, capa); rb_ary_transient_heap_evacuate(ary, TRUE); @@ -794,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; } @@ -837,64 +1028,69 @@ RUBY_FUNC_EXPORTED size_t rb_ary_memsize(VALUE ary) { if (ARY_OWNS_HEAP_P(ary)) { - return ARY_CAPA(ary) * sizeof(VALUE); + return ARY_CAPA(ary) * sizeof(VALUE); } else { - return 0; + return 0; } } -static inline void -ary_discard(VALUE ary) -{ - rb_ary_free(ary); - RBASIC(ary)->flags |= RARRAY_EMBED_FLAG; - RBASIC(ary)->flags &= ~(RARRAY_EMBED_LEN_MASK | RARRAY_TRANSIENT_FLAG); -} - static VALUE ary_make_shared(VALUE ary) { - assert(!ARY_EMBED_P(ary)); + assert(USE_RVARGC || !ARY_EMBED_P(ary)); ary_verify(ary); if (ARY_SHARED_P(ary)) { return ARY_SHARED_ROOT(ary); } else if (ARY_SHARED_ROOT_P(ary)) { - return ary; + return ary; } else if (OBJ_FROZEN(ary)) { - rb_ary_transient_heap_evacuate(ary, TRUE); - ary_shrink_capa(ary); - FL_SET_SHARED_ROOT(ary); - ARY_SET_SHARED_ROOT_REFCNT(ary, 1); - return ary; + if (!ARY_EMBED_P(ary)) { + rb_ary_transient_heap_evacuate(ary, TRUE); + ary_shrink_capa(ary); + } + return ary; } else { - long capa = ARY_CAPA(ary), len = RARRAY_LEN(ary); - const VALUE *ptr; - NEWOBJ_OF(shared, struct RArray, 0, T_ARRAY | (RGENGC_WB_PROTECTED_ARRAY ? FL_WB_PROTECTED : 0)); - VALUE vshared = (VALUE)shared; - rb_ary_transient_heap_evacuate(ary, TRUE); - ptr = ARY_HEAP_PTR(ary); - - FL_UNSET_EMBED(vshared); - ARY_SET_LEN(vshared, capa); - ARY_SET_PTR(vshared, ptr); - ary_mem_clear(vshared, len, capa - len); - FL_SET_SHARED_ROOT(vshared); - ARY_SET_SHARED_ROOT_REFCNT(vshared, 1); - FL_SET_SHARED(ary); + + long capa = ARY_CAPA(ary); + long len = RARRAY_LEN(ary); + + /* Shared roots cannot be embedded because the reference count + * (refcnt) is stored in as.heap.aux.capa. */ + VALUE shared = ary_alloc_heap(0); + FL_SET_SHARED_ROOT(shared); + + if (ARY_EMBED_P(ary)) { + /* Cannot use ary_heap_alloc because we don't want to allocate + * on the transient heap. */ + VALUE *ptr = ALLOC_N(VALUE, capa); + ARY_SET_PTR(shared, ptr); + ary_memcpy(shared, 0, len, RARRAY_PTR(ary)); + + FL_UNSET_EMBED(ary); + ARY_SET_HEAP_LEN(ary, len); + ARY_SET_PTR(ary, ptr); + } + else { + ARY_SET_PTR(shared, RARRAY_PTR(ary)); + } + + ARY_SET_LEN(shared, capa); + ary_mem_clear(shared, len, capa - len); + ARY_SET_SHARED_ROOT_REFCNT(shared, 1); + FL_SET_SHARED(ary); RB_DEBUG_COUNTER_INC(obj_ary_shared_create); - ARY_SET_SHARED(ary, vshared); - OBJ_FREEZE(vshared); + ARY_SET_SHARED(ary, shared); - ary_verify(vshared); + ary_verify(shared); ary_verify(ary); - return vshared; + return shared; } } @@ -903,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; @@ -939,28 +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(object) -> new_array or nil - * - * Tries to convert +object+ to an \Array. + * Array.try_convert(object) -> object, new_array, or nil * - * When +object+ is an - * {Array-convertible object}[doc/implicit_conversion_rdoc.html#label-Array-Convertible+Objects] - * (implements +to_ary+), - * returns the \Array object created by converting it: + * If +object+ is an \Array object, returns +object+. * - * class ToAryReturnsArray < Set - * def to_ary - * self.to_a - * end - * end - * as = ToAryReturnsArray.new([:foo, :bar, :baz]) - * Array.try_convert(as) # => [:foo, :bar, :baz] + * Otherwise if +object+ responds to <tt>:to_ary</tt>, + * calls <tt>object.to_ary</tt> and returns the result. * - * Returns +nil+ if +object+ is not \Array-convertible: + * Returns +nil+ if +object+ does not respond to <tt>:to_ary</tt> * - * Array.try_convert(:foo) # => nil + * Raises an exception unless <tt>object.to_ary</tt> returns an \Array object. */ static VALUE @@ -969,6 +1163,30 @@ rb_ary_s_try_convert(VALUE dummy, VALUE ary) return rb_check_array_type(ary); } +/* :nodoc: */ +static VALUE +rb_ary_s_new(int argc, VALUE *argv, VALUE klass) +{ + VALUE ary; + + if (klass == rb_cArray) { + long size = 0; + if (argc > 0 && FIXNUM_P(argv[0])) { + size = FIX2LONG(argv[0]); + if (size < 0) size = 0; + } + + ary = ary_new(klass, size); + + rb_obj_call_init_kw(ary, argc, argv, RB_PASS_CALLED_KEYWORDS); + } + else { + ary = rb_class_new_instance_pass_kw(argc, argv, klass); + } + + return ary; +} + /* * call-seq: * Array.new -> new_empty_array @@ -979,88 +1197,42 @@ rb_ary_s_try_convert(VALUE dummy, VALUE ary) * * Returns a new \Array. * - * Argument +array+, if given, must be an - * {Array-convertible object}[doc/implicit_conversion_rdoc.html#label-Array-Convertible+Objects] - * (implements +to_ary+). + * With no block and no arguments, returns a new empty \Array object. * - * Argument +size+, if given must be an - * {Integer-convertible object}[doc/implicit_conversion_rdoc.html#label-Integer-Convertible+Objects] - * (implements +to_int+). - * - * Argument +default_value+ may be any object. - * - * --- - * - * With no block and no arguments, returns a new empty \Array object: - * - * a = Array.new - * a # => [] - * - * With no block and a single argument +array+, + * With no block and a single \Array argument +array+, * returns a new \Array formed from +array+: * * a = Array.new([:foo, 'bar', 2]) * a.class # => Array * a # => [:foo, "bar", 2] * - * With no block and a single argument +size+, + * With no block and a single \Integer argument +size+, * returns a new \Array of the given size * whose elements are all +nil+: * - * a = Array.new(0) - * a # => [] * a = Array.new(3) * a # => [nil, nil, nil] * - * With no block and arguments +size+ and +default_value+, + * With no block and arguments +size+ and +default_value+, * returns an \Array of the given size; * each element is that same +default_value+: * * a = Array.new(3, 'x') * a # => ['x', 'x', 'x'] - * a[1].equal?(a[0]) # => true # Identity check. - * a[2].equal?(a[0]) # => true # Identity check. * * 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: * - * a = Array.new(3) { |index| "Element #{index}" } + * a = Array.new(3) {|index| "Element #{index}" } * a # => ["Element 0", "Element 1", "Element 2"] * + * 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: - * - * a = Array.new(0) { |n| raise 'Cannot happen' } - * a # => [] - * a = Array.new { |n| raise 'Cannot happen' } - * a # => [] - * - * With a block and arguments +size+ and +default_value+, - * gives a warning message - * ('warning: block supersedes default value argument'), - * and assigns elements from the block's return values: - * - * Array.new(4, :default) {} # => [nil, nil, nil, nil] - * - * --- - * - * Raises an exception if +size+ is a negative integer: - * - * # Raises ArgumentError (negative array size): - * Array.new(-1) - * # Raises ArgumentError (negative array size): - * Array.new(-1, :default) - * # Raises ArgumentError (negative array size): - * Array.new(-1) { |n| } - * - * Raises an exception if the single argument is neither \Array-convertible - * nor \Integer-convertible. - * - * # Raises TypeError (no implicit conversion of Symbol into Integer): - * Array.new(:foo) + * ignores the block and returns a new empty \Array. */ static VALUE @@ -1071,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; } @@ -1146,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); } @@ -1177,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); @@ -1202,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 @@ -1229,13 +1451,13 @@ ary_take_first_or_last(int argc, const VALUE *argv, VALUE ary, enum ary_take_pos n = NUM2LONG(argv[0]); len = RARRAY_LEN(ary); if (n > len) { - n = len; + n = len; } else if (n < 0) { - rb_raise(rb_eArgError, "negative array size"); + rb_raise(rb_eArgError, "negative array size"); } if (last) { - offset = len - n; + offset = len - n; } return ary_make_partial(ary, rb_cArray, offset, n); } @@ -1245,16 +1467,16 @@ ary_take_first_or_last(int argc, const VALUE *argv, VALUE ary, enum ary_take_pos * array << object -> self * * Appends +object+ to +self+; returns +self+: + * * a = [:foo, 'bar', 2] - * a1 = a << :baz - * a1 # => [:foo, "bar", 2, :baz] - * a1.equal?(a) # => true # Returned self + * a << :baz # => [:foo, "bar", 2, :baz] * * Appends +object+ as one element, even if it is another \Array: * * a = [:foo, 'bar', 2] - * a1 = a << [3, 4] # => + * a1 = a << [3, 4] * a1 # => [:foo, "bar", 2, [3, 4]] + * */ VALUE @@ -1263,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); @@ -1283,26 +1505,23 @@ rb_ary_cat(VALUE ary, const VALUE *argv, long len) /* * call-seq: * array.push(*objects) -> self - * array.append(*objects) -> self * - * Appends trailing elements. #append is an alias for \#push. - - * See also: - * - #pop: Removes and returns trailing elements. - * - #shift: Removes and returns leading elements. - * - #unshift: Prepends leading elements. + * Appends trailing elements. * * Appends each argument in +objects+ to +self+; returns +self+: + * * a = [:foo, 'bar', 2] - * a1 = a.push(:baz, :bat) - * a1 # => [:foo, "bar", 2, :baz, :bat] - * a1.equal?(a) # => true # Returned self + * 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 @@ -1319,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); @@ -1337,63 +1556,28 @@ rb_ary_pop(VALUE ary) * * Removes and returns trailing elements. * - * See also: - * - #push: Appends trailing elements. - * - #shift: Removes and returns leading elements. - * - #unshift: Prepends leading elements. - * - * Argument +n+, if given, must be an - * {Integer-convertible object}[doc/implicit_conversion_rdoc.html#label-Integer-Convertible+Objects] - * (implements +to_int+). - * - * --- - * - * When no argument is given and the array is not empty, - * removes and returns the last element in the array: + * 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"] * - * Returns +nil+ if the array is empty: + * Returns +nil+ if the array is empty. * - * a = [] - * a.pop # => nil - * - * --- - * - * When argument +n+ is given and is non-negative and in range, + * When a non-negative \Integer argument +n+ is given and is in range, * * removes and returns the last +n+ elements in a new \Array: - * * a = [:foo, 'bar', 2] - * a1 = a.pop(2) - * a1 # => ["bar", 2] - * a # => [:foo] - * a.pop(0) # => [] + * a.pop(2) # => ["bar", 2] * * If +n+ is positive and out of range, * removes and returns all elements: * * a = [:foo, 'bar', 2] - * a1 = a.pop(50) - * a1 # => [:foo, "bar", 2] - * a # => [] - * a.pop(1) # => [] - * - * --- - * - * Raises an exception if +n+ is negative: - * - * a = [:foo, 'bar', 2] - * # Raises ArgumentError (negative array size): - * a1 = a.pop(-1) + * a.pop(50) # => [:foo, "bar", 2] * - * Raises an exception if +n+ is not \Integer-convertible (implements +to_int+): - * - * a = [:foo, 'bar', 2] - * # Raises TypeError (no implicit conversion of String into Integer): - * a1 = a.pop('x') + * Related: #push, #shift, #unshift. */ static VALUE @@ -1402,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); @@ -1418,30 +1602,14 @@ rb_ary_shift(VALUE ary) VALUE top; long len = RARRAY_LEN(ary); - rb_ary_modify_check(ary); - if (len == 0) return Qnil; - top = RARRAY_AREF(ary, 0); - if (!ARY_SHARED_P(ary)) { - if (len < ARY_DEFAULT_SIZE) { - RARRAY_PTR_USE_TRANSIENT(ary, ptr, { - MEMMOVE(ptr, ptr+1, VALUE, len-1); - }); /* WB: no new reference */ - ARY_INCREASE_LEN(ary, -1); - ary_verify(ary); - return top; - } - assert(!ARY_EMBED_P(ary)); /* ARY_EMBED_LEN_MAX < ARY_DEFAULT_SIZE */ - - ARY_SET(ary, 0, Qnil); - ary_make_shared(ary); - } - else if (ARY_SHARED_ROOT_OCCUPIED(ARY_SHARED_ROOT(ary))) { - RARRAY_PTR_USE_TRANSIENT(ary, ptr, ptr[0] = Qnil); + if (len == 0) { + rb_ary_modify_check(ary); + return Qnil; } - ARY_INCREASE_PTR(ary, 1); /* shift ptr */ - ARY_INCREASE_LEN(ary, -1); - ary_verify(ary); + top = RARRAY_AREF(ary, 0); + + rb_ary_behead(ary, 1); return top; } @@ -1453,55 +1621,30 @@ rb_ary_shift(VALUE ary) * * Removes and returns leading elements. * - * See also: - * - #push: Appends trailing elements. - * - #pop: Removes and returns trailing elements. - * - #unshift: Prepends leading elements. - * - * Argument +n+, if given, must be an - * {Integer-convertible object}[doc/implicit_conversion_rdoc.html#label-Integer-Convertible+Objects] - * - * --- - * * 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: - * [].shift # => nil - * - * --- + * Returns +nil+ if +self+ is empty. * - * When argument +n+ is given, removes the first +n+ elements; + * When positive \Integer argument +n+ is given, removes the first +n+ elements; * returns those elements in a new \Array: + * * a = [:foo, 'bar', 2] * a.shift(2) # => [:foo, 'bar'] * a # => [2] * * If +n+ is as large as or larger than <tt>self.length</tt>, * removes all elements; returns those elements in a new \Array: - * a = [:foo, 'bar', 2] - * a.shift(3) # => [:foo, 'bar', 2] - * a # => [] * - * If +n+ is 0, returns a new empty \Array; +self+ is unmodified: * a = [:foo, 'bar', 2] - * a.shift(0) # => [] - * a # => [:foo, 'bar', 2] - * - * --- + * a.shift(3) # => [:foo, 'bar', 2] * - * Raises an exception if +n+ is negative: - * a = [:foo, 'bar', 2] - * # Raises ArgumentError (negative array size): - * a1 = a.shift(-1) + * If +n+ is zero, returns a new empty \Array; +self+ is unmodified. * - * Raises an exception if +n+ is not an - * {Integer-convertible object}[doc/implicit_conversion_rdoc.html#label-Integer-Convertible+Objects]: - * a = [:foo, 'bar', 2] - * # Raises TypeError (no implicit conversion of Symbol into Integer): - * a.shift(:foo) + * Related: #push, #pop, #unshift. */ static VALUE @@ -1511,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); @@ -1525,115 +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_ROOT_OCCUPIED(ARY_SHARED_ROOT(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_root = ARY_SHARED_ROOT(ary); - capa = RARRAY_LEN(shared_root); - if (ARY_SHARED_ROOT_OCCUPIED(shared_root) && capa > new_len) { - rb_ary_modify_check(ary); - head = RARRAY_CONST_PTR_TRANSIENT(ary); - sharedp = RARRAY_CONST_PTR_TRANSIENT(shared_root); - 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_ROOT_OCCUPIED(ARY_SHARED_ROOT(ary))); - - ary_verify(ary); - return ARY_SHARED_ROOT(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: * array.unshift(*objects) -> self - * array.prepend(*objects) -> self - * - * Prepends leading elements. #prepend is an alias for \#unshift. - * - * See also: - * - #push: Appends trailing elements. - * - #pop: Removes and returns trailing elements. - * - #shift: Removes and returns leading elements. * * Prepends the given +objects+ to +self+: + * * a = [:foo, 'bar', 2] - * a1 = a.unshift(:bam, :bat) - * a1 # => [:bam, :bat, :foo, "bar", 2] - * a1.equal?(a) # => true # Returned self + * a.unshift(:bam, :bat) # => [:bam, :bat, :foo, "bar", 2] + * + * Array#prepend is an alias for Array#unshift. + * + * Related: #push, #pop, #shift. */ static VALUE @@ -1643,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); @@ -1666,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); } @@ -1678,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); @@ -1687,12 +1848,22 @@ 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); @@ -1702,103 +1873,104 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e); * 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+. * - * - Arguments +index+, +start+, and +length+, if given, must be - * {Integer-convertible objects}[doc/implicit_conversion_rdoc.html#label-Integer-Convertible+Objects]. - * - Argument +range+, if given, must be a \Range object. - * - * --- + * When a single \Integer argument +index+ is given, returns the element at offset +index+: * - * When a single 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+: - * a = [:foo, 'bar', 2] - * a[50] # => nil - * a[-50] # => nil - * - * --- + * If +index+ is out of range, returns +nil+. * - * When two arguments +start+ and +length+ are given, + * 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: - * a = [:foo, 'bar', 2] - * a[a.size, 0] # => [] - * a[a.size, 50] # => [] + * returns a new empty \Array. * - * If +length+ is negative, returns +nil+: - * a = [:foo, 'bar', 2] - * a[2, -1] # => nil - * a[1, -2] # => nil - * - * --- + * If +length+ is negative, returns +nil+. * - * When a single argument +range+ is given, + * 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: - * a = [:foo, 'bar', 2] - * a[a.size..0] # => [] - * a[a.size..50] # => [] - * a[a.size..-1] # => [] - * a[a.size..-50] # => [] + * 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] - * a[0..-4] # => [] * * 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+. * - * Raises an exception if given a single argument - * that is not an \Integer-convertible object or a \Range object: * a = [:foo, 'bar', 2] - * # Raises TypeError (no implicit conversion of Symbol into Integer): - * a[:foo] + * 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: * - * Raises an exception if given two arguments that are not both \Integer-convertible objects: * a = [:foo, 'bar', 2] * # Raises TypeError (no implicit conversion of Symbol into Integer): - * a[:foo, 3] - * # Raises TypeError (no implicit conversion of Symbol into Integer): - * a[1, :bar] + * a[:foo] + * + * Array#slice is an alias for Array#[]. */ VALUE @@ -1806,7 +1978,7 @@ rb_ary_aref(int argc, const VALUE *argv, VALUE ary) { rb_check_arity(argc, 1, 2); if (argc == 2) { - return rb_ary_aref2(ary, argv[0], argv[1]); + return rb_ary_aref2(ary, argv[0], argv[1]); } return rb_ary_aref1(ary, argv[0]); } @@ -1817,7 +1989,7 @@ rb_ary_aref2(VALUE ary, VALUE b, VALUE e) long beg = NUM2LONG(b); long len = NUM2LONG(e); if (beg < 0) { - beg += RARRAY_LEN(ary); + beg += RARRAY_LEN(ary); } return rb_ary_subseq(ary, beg, len); } @@ -1825,21 +1997,22 @@ 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)); } @@ -1847,20 +2020,11 @@ rb_ary_aref1(VALUE ary, VALUE arg) * call-seq: * array.at(index) -> object * - * Argument +index+ must be an - * {Integer-convertible object}[doc/implicit_conversion_rdoc.html#label-Integer-Convertible+Objects]. - * - * Returns the element at offset +index+; does not modify +self+. + * Returns the element at \Integer offset +index+; does not modify +self+. * a = [:foo, 'bar', 2] * a.at(0) # => :foo * a.at(2) # => 2 * - * --- - * - * Raises an exception if +index+ is not an \Integer-convertible object: - * a = [:foo, 'bar', 2] - * # Raises TypeError (no implicit conversion of Symbol into Integer): - * a.at(:foo) */ VALUE @@ -1875,56 +2039,42 @@ rb_ary_at(VALUE ary, VALUE pos) * array.first(n) -> new_array * * Returns elements from +self+; does not modify +self+. - * See also #last. - * - * Argument +n+, if given, must be an - * {Integer-convertible object}[doc/implicit_conversion_rdoc.html#label-Integer-Convertible+Objects]. - * - * --- * * When no argument is given, returns the first element: + * * a = [:foo, 'bar', 2] * a.first # => :foo * a # => [:foo, "bar", 2] * - * If +self+ is empty, returns +nil+: - * [].first # => nil + * If +self+ is empty, returns +nil+. * - * --- + * When non-negative \Integer argument +n+ is given, + * returns the first +n+ elements in a new \Array: * - * When 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 >= ary.size</tt>, returns all elements: + * 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) # [] - * - * --- * - * Raises an exception if +n+ is negative: * a = [:foo, 'bar', 2] - * # Raises ArgumentError (negative array size): - * a.first(-1) + * a.first(0) # [] * - * Raises an exception if +n+ is not an \Integer-convertible object: - * a = [:foo, 'bar', 2] - * # Raises TypeError (no implicit conversion of String into Integer): - * a.first(:X) + * 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); } } @@ -1934,58 +2084,44 @@ rb_ary_first(int argc, VALUE *argv, VALUE ary) * array.last(n) -> new_array * * Returns elements from +self+; +self+ is not modified. - * See also #first. - * - * Argument +n+, if given, must be an - * {Integer-convertible object}[doc/implicit_conversion_rdoc.html#label-Integer-Convertible+Objects]. - * - * --- * * When no argument is given, returns the last element: + * * a = [:foo, 'bar', 2] * a.last # => 2 * a # => [:foo, "bar", 2] * - * If +self+ is empty, returns +nil+: - * [].last # => nil + * If +self+ is empty, returns +nil+. * - * --- + * When non-negative \Integer argument +n+ is given, + * returns the last +n+ elements in a new \Array: * - * When 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 >= ary.size</tt>, returns all elements: + * 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) # [] - * - * --- * - * Raises an exception if +n+ is negative: * a = [:foo, 'bar', 2] - * # Raises ArgumentError (negative array size): - * a.last(-1) + * a.last(0) # [] * - * Raises an exception if +n+ is not an \Integer-convertible object: - * a = [:foo, 'bar', 2] - * # Raises TypeError (no implicit conversion of Symbol into Integer): - * a.last(:X) + * 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); } } @@ -1997,50 +2133,33 @@ rb_ary_last(int argc, const VALUE *argv, VALUE ary) * * Returns the element at offset +index+. * - * Argument +index+ must be an - * {Integer-convertible object}[doc/implicit_conversion_rdoc.html#label-Integer-Convertible+Objects] - * - * --- + * With the single \Integer argument +index+, + * returns the element at offset +index+: * - * With the single 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" - * a.fetch(50, nil) # => nil - * - * --- * * 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" + * a.fetch(1) {|index| raise 'Cannot happen' } # => "bar" + * a.fetch(50) {|index| "Value for #{index}" } # => "Value for 50" * - * --- - * - * Raises an exception if +index+ is not an \Integer-convertible object. - * a = [:foo, 'bar', 2] - * # Raises TypeError (no implicit conversion of Symbol into Integer): - * a.fetch(:foo) - * - * Raises an exception if +index+ is out of range and neither default_value nor a block given: - * a = [:foo, 'bar', 2] - * # Raises IndexError (index 50 outside of array bounds: -3...3): - * a.fetch(50) */ static VALUE @@ -2053,20 +2172,20 @@ rb_ary_fetch(int argc, VALUE *argv, VALUE ary) rb_scan_args(argc, argv, "11", &pos, &ifnone); block_given = rb_block_given_p(); if (block_given && argc == 2) { - rb_warn("block supersedes default value argument"); + rb_warn("block supersedes default value argument"); } idx = NUM2LONG(pos); if (idx < 0) { - idx += RARRAY_LEN(ary); + idx += RARRAY_LEN(ary); } if (idx < 0 || RARRAY_LEN(ary) <= idx) { - if (block_given) return rb_yield(pos); - if (argc == 1) { - rb_raise(rb_eIndexError, "index %ld outside of array bounds: %ld...%ld", - idx - (idx < 0 ? RARRAY_LEN(ary) : 0), -RARRAY_LEN(ary), RARRAY_LEN(ary)); - } - return ifnone; + if (block_given) return rb_yield(pos); + if (argc == 1) { + rb_raise(rb_eIndexError, "index %ld outside of array bounds: %ld...%ld", + idx - (idx < 0 ? RARRAY_LEN(ary) : 0), -RARRAY_LEN(ary), RARRAY_LEN(ary)); + } + return ifnone; } return RARRAY_AREF(ary, idx); } @@ -2076,52 +2195,37 @@ rb_ary_fetch(int argc, VALUE *argv, VALUE ary) * array.index(object) -> integer or nil * array.index {|element| ... } -> integer or nil * array.index -> new_enumerator - * array.find_index(object) -> integer or nil - * array.find_index {|element| ... } -> integer or nil - * array.find_index -> new_enumerator - * - * Array#index is an alias for Array#find_index. - * See also Array#rindex. * - * --- + * 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: - * a = [:foo, 'bar', 2] - * a.index(:nosuch) # => nil - * - * --- + * Returns +nil+ if no such element found. * * When both argument +object+ and a block are given, * calls the block with each successive element; * returns the index of the first element for which the block returns a truthy value: - * a = [:foo, 'bar', 2, 'bar'] - * a.index { |element| element == 'bar' } # => 1 * - * Returns +nil+ if the block never returns a truthy value: - * a = [:foo, 'bar', 2] - * a.index { |element| element == :X } # => nil + * a = [:foo, 'bar', 2, 'bar'] + * a.index {|element| element == 'bar' } # => 1 * - * --- + * Returns +nil+ if the block never returns a truthy value. * * When neither an argument nor a block is given, returns a new Enumerator: + * * a = [:foo, 'bar', 2] * e = a.index * e # => #<Enumerator: [:foo, "bar", 2]:index> - * e.each { |element| element == 'bar' } # => 1 + * e.each {|element| element == 'bar' } # => 1 * - * --- + * Array#find_index is an alias for Array#index. * - * When both an argument and a block given, gives a warning (warning: given block not used) - * and ignores the block: - * a = [:foo, 'bar', 2, 'bar'] - * index = a.index('bar') { raise 'Cannot happen' } - * index # => 1 + * Related: #rindex. */ static VALUE @@ -2131,23 +2235,23 @@ rb_ary_index(int argc, VALUE *argv, VALUE ary) long i; if (argc == 0) { - RETURN_ENUMERATOR(ary, 0, 0); - for (i=0; i<RARRAY_LEN(ary); i++) { - if (RTEST(rb_yield(RARRAY_AREF(ary, i)))) { - return LONG2NUM(i); - } - } - return Qnil; + RETURN_ENUMERATOR(ary, 0, 0); + for (i=0; i<RARRAY_LEN(ary); i++) { + if (RTEST(rb_yield(RARRAY_AREF(ary, i)))) { + return LONG2NUM(i); + } + } + return Qnil; } rb_check_arity(argc, 0, 1); val = argv[0]; if (rb_block_given_p()) - rb_warn("given block not used"); + rb_warn("given block not used"); for (i=0; i<RARRAY_LEN(ary); i++) { - VALUE e = RARRAY_AREF(ary, i); - if (rb_equal(e, val)) { - return LONG2NUM(i); - } + VALUE e = RARRAY_AREF(ary, i); + if (rb_equal(e, val)) { + return LONG2NUM(i); + } } return Qnil; } @@ -2160,44 +2264,29 @@ rb_ary_index(int argc, VALUE *argv, VALUE ary) * * Returns the index of the last element for which <tt>object == element</tt>. * - * --- - * * When argument +object+ is given but no block, returns the index of the last such element found: + * * a = [:foo, 'bar', 2, 'bar'] * a.rindex('bar') # => 3 * - * Returns +nil+ if no such object found: - * a = [:foo, 'bar', 2] - * a.rindex(:nosuch) # => nil - * - * --- + * Returns +nil+ if no such object 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: + * * a = [:foo, 'bar', 2, 'bar'] * a.rindex {|element| element == 'bar' } # => 3 * - * Returns +nil+ if the block never returns a truthy value: - * - * a = [:foo, 'bar', 2] - * a.rindex {|element| element == :X } # => nil - * - * --- + * Returns +nil+ if the block never returns a truthy value. * * When neither an argument nor a block is given, returns a new \Enumerator: * * a = [:foo, 'bar', 2, 'bar'] * e = a.rindex * e # => #<Enumerator: [:foo, "bar", 2, "bar"]:rindex> - * e.each { |element| element == 'bar' } # => 3 + * e.each {|element| element == 'bar' } # => 3 * - * --- - * - * When both an argument and a block given, gives a warning (warning: given block not used) - * and ignores the block: - * a = [:foo, 'bar', 2, 'bar'] - * index = a.rindex('bar') { raise 'Cannot happen' } - * index # => 3 + * Related: #index. */ static VALUE @@ -2207,25 +2296,25 @@ rb_ary_rindex(int argc, VALUE *argv, VALUE ary) long i = RARRAY_LEN(ary), len; if (argc == 0) { - RETURN_ENUMERATOR(ary, 0, 0); - while (i--) { - if (RTEST(rb_yield(RARRAY_AREF(ary, i)))) - return LONG2NUM(i); - if (i > (len = RARRAY_LEN(ary))) { - i = len; - } - } - return Qnil; + RETURN_ENUMERATOR(ary, 0, 0); + while (i--) { + if (RTEST(rb_yield(RARRAY_AREF(ary, i)))) + return LONG2NUM(i); + if (i > (len = RARRAY_LEN(ary))) { + i = len; + } + } + return Qnil; } rb_check_arity(argc, 0, 1); val = argv[0]; if (rb_block_given_p()) - rb_warn("given block not used"); + rb_warn("given block not used"); while (i--) { - VALUE e = RARRAY_AREF(ary, i); - if (rb_equal(e, val)) { - return LONG2NUM(i); - } + VALUE e = RARRAY_AREF(ary, i); + if (rb_equal(e, val)) { + return LONG2NUM(i); + } if (i > RARRAY_LEN(ary)) { break; } @@ -2251,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); @@ -2308,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)); - } + } } } @@ -2319,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) { @@ -2344,36 +2425,58 @@ 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: * array[index] = object -> object @@ -2382,42 +2485,36 @@ rb_ary_resize(VALUE ary, long len) * * Assigns elements in +self+; returns the given +object+. * - * - Arguments +index+, +start+, and +length+, if given, must be - * {Integer-convertible objects}[doc/implicit_conversion_rdoc.html#label-Integer-Convertible+Objects]. - * - Argument +range+, if given, must be a \Range object. - * - If +object+ is an - * {Array-convertible object}[doc/implicit_conversion_rdoc.html#label-Array-Convertible+Objects] - * it will be converted to an \Array. - * - * --- - * - * When +index+ is given, assigns +object+ to an element in +self+. + * 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 +start+ and +length+ are given and +object+ is not an Array-convertible object, + * 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"] @@ -2425,49 +2522,55 @@ rb_ary_resize(VALUE ary, long len) * 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+ is given and +object+ is an \Array-convertible object, + * 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"] @@ -2481,124 +2584,72 @@ rb_ary_resize(VALUE ary, long len) * * 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"] * - * --- - * - * Raises an exception if given a single argument - * that is not an \Integer-convertible object or a \Range: - * a = [:foo, 'bar', 2] - * # Raises TypeError (no implicit conversion of Symbol into Integer): - * a[:nosuch] = 'two' - * - * Raises an exception if given two arguments that are not both \Integer-convertible objects: - * a = [:foo, 'bar', 2] - * # Raises TypeError (no implicit conversion of Symbol into Integer): - * a[:nosuch, 2] = 'two' - * # Raises TypeError (no implicit conversion of Symbol into Integer): - * a[0, :nosuch] = 'two' - * - * Raises an exception if a negative +index+ is out of range: - * a = [:foo, 'bar', 2] - * # Raises IndexError (index -4 too small for array; minimum: -3): - * a[-4] = 'two' - * - * Raises an exception if +start+ is too small for the array: - * a = [:foo, 'bar', 2] - * # Raises IndexError (index -5 too small for array; minimum: -3): - * a[-5, 2] = 'foo' - * - * Raises an exception if +length+ is negative: - * a = [:foo, 'bar', 2] - * # Raises IndexError (negative length (-1)): - * a[1, -1] = '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: * array.insert(index, *objects) -> self * - * Inserts given +objects+ before or after the element at +offset+ index; + * Inserts given +objects+ before or after the element at \Integer index +offset+; * returns +self+. * - * Argument +index+ must be an - * {Integer-convertible object}[doc/implicit_conversion_rdoc.html#label-Integer-Convertible+Objects]. - * - * --- - * * When +index+ is non-negative, inserts all given +objects+ * before the element at offset +index+: + * * a = [:foo, 'bar', 2] - * a1 = a.insert(1, :bat, :bam) - * a # => [:foo, :bat, :bam, "bar", 2] - * a1.object_id == a.object_id # => true + * a.insert(1, :bat, :bam) # => [:foo, :bat, :bam, "bar", 2] * * Extends the array if +index+ is beyond the array (<tt>index >= self.size</tt>): + * * a = [:foo, 'bar', 2] * a.insert(5, :bat, :bam) * a # => [:foo, "bar", 2, nil, nil, :bat, :bam] * * Does nothing if no objects given: + * * a = [:foo, 'bar', 2] * a.insert(1) * a.insert(50) * 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] * - * --- - * - * Raises an exception if +index+ is not an Integer-convertible object: - * a = [:foo, 'bar', 2, 'bar'] - * # Raises TypeError (no implicit conversion of Symbol into Integer): - * a.insert(:foo) - * - * Raises an exception if +index+ is too small (<tt>index+self.size < 0</tt>): - * a = [:foo, 'bar', 2] - * # Raises IndexError (index -5 too small for array; minimum: -4): - * a.insert(-5, :bat, :bam) */ static VALUE @@ -2611,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; @@ -2641,40 +2692,42 @@ ary_enum_length(VALUE ary, VALUE args, VALUE eobj) * * Iterates over array elements. * - * --- - * * When a block given, passes each successive array element to the block; * returns +self+: + * * a = [:foo, 'bar', 2] - * a1 = a.each {|element| puts "#{element.class} #{element}" } - * a1.equal?(a) # => true # Returned self + * a.each {|element| puts "#{element.class} #{element}" } * * Output: + * * Symbol foo * String bar * Integer 2 * * Allows the array to be modified during iteration: + * * a = [:foo, 'bar', 2] * a.each {|element| puts element; a.clear if element.to_s.start_with?('b') } - * a # => [] * * 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}" } + * a1 = e.each {|element| puts "#{element.class} #{element}" } * * Output: + * * Symbol foo * String bar * Integer 2 + * + * Related: #each_index, #reverse_each. */ VALUE @@ -2684,7 +2737,7 @@ rb_ary_each(VALUE ary) ary_verify(ary); RETURN_SIZED_ENUMERATOR(ary, 0, 0, ary_enum_length); for (i=0; i<RARRAY_LEN(ary); i++) { - rb_yield(RARRAY_AREF(ary, i)); + rb_yield(RARRAY_AREF(ary, i)); } return ary; } @@ -2696,40 +2749,42 @@ rb_ary_each(VALUE ary) * * Iterates over array indexes. * - * --- - * * When a block given, passes each successive array index to the block; * returns +self+: + * * a = [:foo, 'bar', 2] - * a1 = a.each_index {|index| puts "#{index} #{a[index]}" } - * a1.equal?(a) # => true # Returned self + * a.each_index {|index| puts "#{index} #{a[index]}" } * * Output: + * * 0 foo * 1 bar * 2 2 * * Allows the array to be modified during iteration: + * * a = [:foo, 'bar', 2] * a.each_index {|index| puts index; a.clear if index > 0 } - * a # => [] * * 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 @@ -2739,7 +2794,7 @@ rb_ary_each_index(VALUE ary) RETURN_SIZED_ENUMERATOR(ary, 0, 0, ary_enum_length); for (i=0; i<RARRAY_LEN(ary); i++) { - rb_yield(LONG2NUM(i)); + rb_yield(LONG2NUM(i)); } return ary; } @@ -2751,39 +2806,42 @@ rb_ary_each_index(VALUE ary) * * Iterates backwards over array elements. * - * --- - * * When a block given, passes, in reverse order, each element to the block; * returns +self+: + * * a = [:foo, 'bar', 2] - * a1 = a.reverse_each {|element| puts "#{element.class} #{element}" } - * a1.equal?(a) # => true # Returned self + * a.reverse_each {|element| puts "#{element.class} #{element}" } * * Output: + * * Integer 2 * String bar * Symbol foo * * Allows the array to be modified during iteration: + * * a = [:foo, 'bar', 2] * a.reverse_each {|element| puts element; a.clear if element.to_s.start_with?('b') } - * 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}" } + * a1 = e.each {|element| puts "#{element.class} #{element}" } + * * Output: + * * Integer 2 * String bar * Symbol foo + * + * Related: #each, #each_index. */ static VALUE @@ -2794,12 +2852,12 @@ rb_ary_reverse_each(VALUE ary) RETURN_SIZED_ENUMERATOR(ary, 0, 0, ary_enum_length); len = RARRAY_LEN(ary); while (len--) { - long nlen; - rb_yield(RARRAY_AREF(ary, len)); - nlen = RARRAY_LEN(ary); - if (nlen < len) { - len = nlen; - } + long nlen; + rb_yield(RARRAY_AREF(ary, len)); + nlen = RARRAY_LEN(ary); + if (nlen < len) { + len = nlen; + } } return ary; } @@ -2808,10 +2866,7 @@ rb_ary_reverse_each(VALUE ary) * call-seq: * array.length -> an_integer * - * Returns the count of elements in the array: - * a = [:foo, 'bar', 2] - * a.length # => 3 - * [].length # => 0 + * Returns the count of elements in +self+. */ static VALUE @@ -2825,18 +2880,14 @@ rb_ary_length(VALUE ary) * call-seq: * array.empty? -> true or false * - * Returns +true+ if the count of elements in the array is zero, - * +false+ otherwise: - * [].empty? # => true - * [:foo, 'bar', 2].empty? # => false + * 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 @@ -2872,10 +2923,10 @@ recursive_join(VALUE obj, VALUE argp, int recur) int *first = (int *)arg[3]; if (recur) { - rb_raise(rb_eArgError, "recursive array join"); + rb_raise(rb_eArgError, "recursive array join"); } else { - ary_join_1(obj, ary, sep, 0, result, first); + ary_join_1(obj, ary, sep, 0, result, first); } return Qnil; } @@ -2888,65 +2939,68 @@ ary_join_0(VALUE ary, VALUE sep, long max, VALUE result) if (max > 0) rb_enc_copy(result, RARRAY_AREF(ary, 0)); for (i=0; i<max; i++) { - val = RARRAY_AREF(ary, i); + val = RARRAY_AREF(ary, i); if (!RB_TYPE_P(val, T_STRING)) break; - if (i > 0 && !NIL_P(sep)) - rb_str_buf_append(result, sep); - rb_str_buf_append(result, val); + if (i > 0 && !NIL_P(sep)) + rb_str_buf_append(result, sep); + rb_str_buf_append(result, val); } return i; } 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); + } +} + +static void 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); + } } } @@ -2959,26 +3013,26 @@ rb_ary_join(VALUE ary, VALUE sep) if (RARRAY_LEN(ary) == 0) return rb_usascii_str_new(0, 0); if (!NIL_P(sep)) { - StringValue(sep); - len += RSTRING_LEN(sep) * (RARRAY_LEN(ary) - 1); + StringValue(sep); + len += RSTRING_LEN(sep) * (RARRAY_LEN(ary) - 1); } for (i=0; i<RARRAY_LEN(ary); i++) { - val = RARRAY_AREF(ary, i); - tmp = rb_check_string_type(val); + val = RARRAY_AREF(ary, i); + tmp = rb_check_string_type(val); - if (NIL_P(tmp) || tmp != val) { - int first; + if (NIL_P(tmp) || tmp != val) { + int first; long n = RARRAY_LEN(ary); if (i > n) i = n; result = rb_str_buf_new(len + (n-i)*10); - rb_enc_associate(result, rb_usascii_encoding()); + rb_enc_associate(result, rb_usascii_encoding()); i = ary_join_0(ary, sep, i, result); - first = i == 0; - ary_join_1(ary, ary, sep, i, result, &first); - return result; - } + first = i == 0; + ary_join_1(ary, ary, sep, i, result, &first); + return result; + } - len += RSTRING_LEN(tmp); + len += RSTRING_LEN(tmp); } result = rb_str_new(0, len); @@ -2995,41 +3049,27 @@ rb_ary_join(VALUE ary, VALUE sep) * array.join(separator = $,) -> new_string * * Returns the new \String formed by joining the array elements after conversion. - * For each element +element+ + * For each element +element+: + * * - Uses <tt>element.to_s</tt> if +element+ is not a <tt>kind_of?(Array)</tt>. * - Uses recursive <tt>element.join(separator)</tt> if +element+ is a <tt>kind_of?(Array)</tt>. * - * Argument +separator+, if given, must be a - * {String-convertible object}[doc/implicit_conversion_rdoc.html#label-String-Convertible+Objects]. - * - * --- - * * With no argument, joins using the output field separator, <tt>$,</tt>: + * * a = [:foo, 'bar', 2] * $, # => nil * a.join # => "foobar2" * - * With argument +separator+, joins using that separator: + * With \string argument +separator+, joins using that separator: + * * a = [:foo, 'bar', 2] * a.join("\n") # => "foo\nbar\n2" * - * --- - * * Joins recursively for nested Arrays: + * * a = [:foo, [:bar, [:baz, :bat]]] * a.join # => "foobarbazbat" * - * --- - * - * Raises an exception if +separator+ is not a String-convertible object: - * a = [:foo, 'bar', 2] - * # Raises TypeError (no implicit conversion of Symbol into String): - * a.join(:foo) - * - * Raises an exception if any element lacks instance method +#to_s+: - * a = [:foo, 'bar', 2, BasicObject.new] - * # Raises NoMethodError (undefined method `to_s' for #<BasicObject>): - * a.join */ static VALUE rb_ary_join_m(int argc, VALUE *argv, VALUE ary) @@ -3039,7 +3079,7 @@ rb_ary_join_m(int argc, VALUE *argv, VALUE ary) if (rb_check_arity(argc, 0, 1) == 0 || NIL_P(sep = argv[0])) { sep = rb_output_fs; if (!NIL_P(sep)) { - rb_warn("$, is set to non-nil value"); + rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, "$, is set to non-nil value"); } } @@ -3055,10 +3095,10 @@ inspect_ary(VALUE ary, VALUE dummy, int recur) if (recur) return rb_usascii_str_new_cstr("[...]"); str = rb_str_buf_new2("["); for (i=0; i<RARRAY_LEN(ary); i++) { - s = rb_inspect(RARRAY_AREF(ary, i)); - if (i > 0) rb_str_buf_cat2(str, ", "); - else rb_enc_copy(str, s); - rb_str_buf_append(str, s); + s = rb_inspect(RARRAY_AREF(ary, i)); + if (i > 0) rb_str_buf_cat2(str, ", "); + else rb_enc_copy(str, s); + rb_str_buf_append(str, s); } rb_str_buf_cat2(str, "]"); return str; @@ -3067,17 +3107,14 @@ inspect_ary(VALUE ary, VALUE dummy, int recur) /* * call-seq: * array.inspect -> new_string - * array.to_s => new_string * - * Returns the new String formed by calling method <tt>#inspect</tt> + * Returns the new \String formed by calling method <tt>#inspect</tt> * on each array element: + * * a = [:foo, 'bar', 2] - * a.inspect # => "[:foo, \"bar\", 2]" + * a.inspect # => "[:foo, \"bar\", 2]" * - * Raises an exception if any element lacks instance method <tt>#inspect</tt>: - * a = [:foo, 'bar', 2, BasicObject.new] - * a.inspect - * # Raises NoMethodError (undefined method `inspect' for #<BasicObject>) + * Array#to_s is an alias for Array#inspect. */ static VALUE @@ -3098,12 +3135,12 @@ rb_ary_to_s(VALUE ary) * to_a -> self or new_array * * When +self+ is an instance of \Array, returns +self+: + * * a = [:foo, 'bar', 2] - * a.instance_of?(Array) # => true - * a1 = a.to_a - * a1.equal?(a) # => true # Returned self + * 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 @@ -3111,15 +3148,16 @@ rb_ary_to_s(VALUE ary) * a1 = a.to_a * a1 # => ["foo", "bar", "two"] * a1.class # => Array # Not MyArray + * */ static VALUE rb_ary_to_a(VALUE ary) { if (rb_obj_class(ary) != rb_cArray) { - VALUE dup = rb_ary_new2(RARRAY_LEN(ary)); - rb_ary_replace(dup, ary); - return dup; + VALUE dup = rb_ary_new2(RARRAY_LEN(ary)); + rb_ary_replace(dup, ary); + return dup; } return ary; } @@ -3134,30 +3172,19 @@ rb_ary_to_a(VALUE ary) * When a block is given, calls the block with each array element; * the block must return a 2-element \Array whose two elements * form a key-value pair in the returned \Hash: + * * a = ['foo', :bar, 1, [2, 3], {baz: 4}] * h = a.to_h {|item| [item, item] } * h # => {"foo"=>"foo", :bar=>:bar, 1=>1, [2, 3]=>[2, 3], {:baz=>4}=>{:baz=>4}} * * When no block is given, +self+ must be an \Array of 2-element sub-arrays, * each sub-array is formed into a key-value pair in the new \Hash: + * * [].to_h # => {} * a = [['foo', 'zero'], ['bar', 'one'], ['baz', 'two']] * h = a.to_h * h # => {"foo"=>"zero", "bar"=>"one", "baz"=>"two"} * - * --- - * - * Raises an exception if no block is given - * and any element in +self+ is not a 2-element \Array: - * # Raises TypeError (wrong element type Symbol at 0 (expected array): - * [:foo].to_h - * # Raises ArgumentError (wrong array length at 0 (expected 2, was 1)): - * [[:foo]].to_h - * - * Raises an exception if for some 2-element \Array +element+ in +self+, - * <tt>element.first</tt> would be an invalid hash key: - * # Raises NoMethodError (undefined method `hash' for #<BasicObject:>): - * [[BasicObject.new, 0]].to_h */ static VALUE @@ -3168,18 +3195,18 @@ rb_ary_to_h(VALUE ary) int block_given = rb_block_given_p(); for (i=0; i<RARRAY_LEN(ary); i++) { - const VALUE e = rb_ary_elt(ary, i); - const VALUE elt = block_given ? rb_yield_force_blockarg(e) : e; - const VALUE key_value_pair = rb_check_array_type(elt); - if (NIL_P(key_value_pair)) { - rb_raise(rb_eTypeError, "wrong element type %"PRIsVALUE" at %ld (expected array)", - rb_obj_class(elt), i); - } - if (RARRAY_LEN(key_value_pair) != 2) { - rb_raise(rb_eArgError, "wrong array length at %ld (expected 2, was %ld)", - i, RARRAY_LEN(key_value_pair)); - } - rb_hash_aset(hash, RARRAY_AREF(key_value_pair, 0), RARRAY_AREF(key_value_pair, 1)); + const VALUE e = rb_ary_elt(ary, i); + const VALUE elt = block_given ? rb_yield_force_blockarg(e) : e; + const VALUE key_value_pair = rb_check_array_type(elt); + if (NIL_P(key_value_pair)) { + rb_raise(rb_eTypeError, "wrong element type %"PRIsVALUE" at %ld (expected array)", + rb_obj_class(elt), i); + } + if (RARRAY_LEN(key_value_pair) != 2) { + rb_raise(rb_eArgError, "wrong array length at %ld (expected 2, was %ld)", + i, RARRAY_LEN(key_value_pair)); + } + rb_hash_aset(hash, RARRAY_AREF(key_value_pair, 0), RARRAY_AREF(key_value_pair, 1)); } return hash; } @@ -3188,10 +3215,7 @@ rb_ary_to_h(VALUE ary) * call-seq: * array.to_ary -> self * - * Returns +self+: - * a = [:foo, 'bar', 2] - * a1 = a.to_ary - * a1.equal?(a) # => true # Returned self + * Returns +self+. */ static VALUE @@ -3204,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; } } @@ -3221,7 +3245,7 @@ rb_ary_reverse(VALUE ary) RARRAY_PTR_USE_TRANSIENT(ary, p1, { p2 = p1 + len - 1; /* points last item */ ary_reverse(p1, p2); - }); /* WB: no new reference */ + }); /* WB: no new reference */ } return ary; } @@ -3231,10 +3255,10 @@ rb_ary_reverse(VALUE ary) * array.reverse! -> self * * Reverses +self+ in place: + * * a = ['foo', 'bar', 'two'] - * a1 = a.reverse! - * a1 # => ["two", "bar", "foo"] - * a1.equal?(a) # => true # Returned self + * a.reverse! # => ["two", "bar", "foo"] + * */ static VALUE @@ -3247,10 +3271,12 @@ rb_ary_reverse_bang(VALUE ary) * call-seq: * array.reverse -> new_array * - * Returns a new \Array whose elements are in reverse order: + * Returns a new \Array with the elements of +self+ in reverse order: + * * a = ['foo', 'bar', 'two'] * a1 = a.reverse * a1 # => ["two", "bar", "foo"] + * */ static VALUE @@ -3262,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; @@ -3281,11 +3307,13 @@ ary_rotate_ptr(VALUE *ptr, long len, long cnt) VALUE tmp = *ptr; memmove(ptr, ptr + 1, sizeof(VALUE)*(len - 1)); *(ptr + len - 1) = tmp; - } else if (cnt == len - 1) { + } + else if (cnt == len - 1) { VALUE tmp = *(ptr + len - 1); memmove(ptr + 1, ptr, sizeof(VALUE)*(len - 1)); *ptr = tmp; - } else { + } + else { --len; if (cnt < len) ary_reverse(ptr + cnt, ptr + len); if (--cnt > 0) ary_reverse(ptr, ptr + cnt); @@ -3310,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 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] * - * If +count+ is negative then it rotates in the opposite direction, starting - * from the end of the array where +-1+ is the last element. + * 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 @@ -3335,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. * - * Returns a new array by rotating +self+ so that the element at +count+ is - * the first element of the new array. + * When no argument given, returns a new \Array that is like +self+, + * except that the first element has been rotated to the last position: + * + * a = [:foo, 'bar', 2, 'bar'] + * a1 = a.rotate + * a1 # => ["bar", 2, "bar", :foo] + * + * When given a non-negative \Integer +count+, + * returns a new \Array with +count+ elements rotated from the beginning to the end: + * + * a = [:foo, 'bar', 2] + * a1 = a.rotate(2) + * a1 # => [2, :foo, "bar"] + * + * If +count+ is large, uses <tt>count % array.size</tt> as the count: + * + * a = [:foo, 'bar', 2] + * a1 = a.rotate(20) + * a1 # => [2, :foo, "bar"] + * + * If +count+ is zero, returns a copy of +self+, unmodified: + * + * a = [:foo, 'bar', 2] + * a1 = a.rotate(0) + * a1 # => [:foo, "bar", 2] + * + * When given a negative \Integer +count+, rotates in the opposite direction, + * from end to beginning: + * + * a = [:foo, 'bar', 2] + * a1 = a.rotate(-2) + * a1 # => ["bar", 2, :foo] * - * If +count+ is negative then it rotates in the opposite direction, starting - * from the end of +self+ where +-1+ is the last element. + * 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 @@ -3361,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; @@ -3373,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) { @@ -3398,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; } @@ -3410,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, Integer)) { - if ((long)a > (long)b) return 1; - if ((long)a < (long)b) return -1; - return 0; + if (FIXNUM_P(a) && FIXNUM_P(b) && CMP_OPTIMIZABLE(INTEGER)) { + if ((long)a > (long)b) return 1; + if ((long)a < (long)b) return -1; + return 0; } - if (STRING_P(a) && STRING_P(b) && CMP_OPTIMIZABLE(data->cmp_opt, String)) { - return rb_str_cmp(a, b); + if (STRING_P(a) && STRING_P(b) && CMP_OPTIMIZABLE(STRING)) { + return rb_str_cmp(a, b); } - if (RB_FLOAT_TYPE_P(a) && CMP_OPTIMIZABLE(data->cmp_opt, Float)) { - return rb_float_cmp(a, b); + if (RB_FLOAT_TYPE_P(a) && CMP_OPTIMIZABLE(FLOAT)) { + return rb_float_cmp(a, b); } retval = rb_funcallv(a, id_cmp, 1, &b); 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 @@ -3459,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 { @@ -3515,31 +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+. * - * To produce the reverse order, the following can also be used - * (and may be faster): + * Example: * - * ary.sort.reverse! #=> ["e", "d", "c", "b", "a"] + * 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"] * - * See also Enumerable#sort_by. + * 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 @@ -3554,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 @@ -3611,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 - * - * 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. + * array.bsearch_index {|element| ... } -> integer or nil + * array.bsearch_index -> new_enumerator * - * 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 @@ -3638,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); @@ -3685,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. * - * Sorts +self+ in place using a set of keys generated by mapping the - * values in +self+ through the given block. + * Calls the block with each successive element; + * sorts elements based on the values returned from the block. * - * The result is not guaranteed to be stable. When two keys are equal, - * the order of the corresponding elements is unpredictable. + * For duplicates returned by the block, the ordering is indeterminate, and may be unstable. * - * If no block is given, an Enumerator is returned instead. + * This example sorts strings based on their sizes: + * + * a = ['aaaa', 'bbb', 'cc', 'd'] + * a.sort_by! {|element| element.size } + * a # => ["d", "cc", "bbb", "aaaa"] + * + * Returns a new \Enumerator if no block given: + * + * a = ['aaaa', 'bbb', 'cc', 'd'] + * a.sort_by! # => #<Enumerator: ["aaaa", "bbb", "cc", "d"]:sort_by!> * - * See also Enumerable#sort_by. */ static VALUE @@ -3714,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. - * - * See also Enumerable#collect. + * a = [:foo, 'bar', 2] + * a1 = a.map {|element| element.class } + * a1 # => [Symbol, String, Integer] * - * If no block is given, an Enumerator is returned instead. + * Returns a new \Enumerator if no block given: + * a = [:foo, 'bar', 2] + * a1 = a.map + * a1 # => #<Enumerator: [:foo, "bar", 2]:map> * - * 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 @@ -3750,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 * - * Invokes the given block once for each element of +self+, replacing the - * element with the value returned by the block. + * Calls the block, if given, with each element; + * replaces the element with the block's return value: * - * See also Enumerable#collect. + * a = [:foo, 'bar', 2] + * a.map! { |element| element.class } # => [Symbol, String, Integer] * - * If no block is given, an Enumerator is returned instead. + * Returns a new \Enumerator if no block given: * - * 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!"] + * a = [:foo, 'bar', 2] + * a1 = a.map! + * a1 # => #<Enumerator: [:foo, "bar", 2]:map!> + * + * Array#collect! is an alias for Array#map!. */ static VALUE @@ -3777,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; } @@ -3789,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; } @@ -3813,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+: + * + * a = [:foo, 'bar', 2] + * a.values_at(0, 2) # => [:foo, 2] + * a.values_at(0..1) # => [:foo, "bar"] + * + * The given +indexes+ may be in any order, and may repeat: * - * Returns an array containing the elements in +self+ corresponding to the - * given +selector+(s). + * 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. * - * The selectors may be either integer indices or ranges. + * For each negative +index+, counts backward from the end of the array: * - * See also Array#select. + * 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 @@ -3860,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; @@ -3869,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. */ @@ -3898,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; } @@ -3918,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; } @@ -3937,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 * - * Invokes the given block passing in successive elements from +self+, - * deleting elements for which the block returns a +false+ value. + * Calls the block, if given with each element of +self+; + * removes from +self+ those elements for which the block returns +false+ or +nil+. * - * The array may not be changed instantly every time the block is called. + * Returns +self+ if any elements were removed: * - * If changes were made, it will return +self+, otherwise it returns +nil+. + * a = [:foo, 'bar', 2, :bam] + * a.select! {|element| element.to_s.start_with?('b') } # => ["bar", :bam] * - * If no block is given, an Enumerator is returned instead. + * Returns +nil+ if no elements were removed. * - * See also Array#keep_if. + * Returns a new \Enumerator if no block given: + * + * a = [:foo, 'bar', 2, :bam] + * a.select! # => #<Enumerator: [:foo, "bar", 2, :bam]:select!> * * Array#filter! is an alias for Array#select!. */ @@ -3985,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 @@ -4013,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: * - * Deletes all items from +self+ that are equal to +obj+. + * s1 = 'bar'; s2 = 'bar' + * a = [:foo, s1, 2, s2] + * a.delete('bar') # => "bar" + * a # => [:foo, 2] * - * Returns the last deleted item, or +nil+ if no matching item is found. + * Returns +nil+ if no elements removed. * - * 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!) + * When a block is given, + * removes from +self+ each element +ele+ such that <tt>ele == obj</tt>. + * + * If any such elements are found, ignores the block + * and returns the last deleted element: + * + * s1 = 'bar'; s2 = 'bar' + * a = [:foo, s1, 2, s2] + * deleted_obj = a.delete('bar') {|obj| fail 'Cannot happen' } + * a # => [:foo, 2] + * + * If no such elements are found, returns the block's return value: + * + * a = [:foo, 'bar', 2] + * a.delete(:nosuch) {|obj| "#{obj} not found" } # => "nosuch not found" * - * 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 @@ -4048,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); @@ -4078,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); @@ -4103,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); @@ -4119,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+. + * + * When +index+ is non-negative, deletes the element at offset +index+: + * + * a = [:foo, 'bar', 2] + * a.delete_at(1) # => "bar" + * a # => [:foo, 2] * - * Deletes the element at the specified +index+, returning that element, or - * +nil+ if the +index+ is out of range. + * If index is too large, returns +nil+. * - * See also Array#slice! + * When +index+ is negative, counts backward from the end of the array: * - * a = ["ant", "bat", "cat", "dog"] - * a.delete_at(2) #=> "cat" - * a #=> ["ant", "bat", "dog"] - * a.delete_at(99) #=> nil + * a = [:foo, 'bar', 2] + * a.delete_at(-2) # => "bar" + * a # => [:foo, 2] + * + * If +index+ is too small (far from zero), returns nil. */ static VALUE @@ -4138,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)); @@ -4213,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; } @@ -4230,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; } @@ -4252,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 * - * Deletes every element of +self+ for which the block evaluates to +true+, - * if no changes were made returns +nil+. + * Removes each element for which the block returns a truthy value. * - * The array may not be changed instantly every time the block is called. + * Returns +self+ if any elements removed: * - * See also Enumerable#reject and Array#delete_if. + * a = [:foo, 'bar', 2, 'bat'] + * a.reject! {|element| element.to_s.start_with?('b') } # => [:foo, 2] + * + * Returns +nil+ if no elements removed. + * + * Returns a new \Enumerator if no block given: + * + * a = [:foo, 'bar', 2] + * a.reject! # => #<Enumerator: [:foo, "bar", 2]:reject!> * - * If no block is given, an Enumerator is returned instead. */ static VALUE @@ -4275,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+: * - * 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. + * a = [:foo, 'bar', 2, 'bat'] + * a1 = a.reject {|element| element.to_s.start_with?('b') } + * a1 # => [:foo, 2] * - * See also 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 @@ -4299,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) @@ -4328,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; } @@ -4341,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+: * - * Converts any arguments to arrays, then merges elements of +self+ with - * corresponding elements from each argument. + * 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]] * - * This generates a sequence of <code>ary.size</code> _n_-element arrays, - * where _n_ is one more than the count of arguments. + * If any array in +other_arrays+ is larger than +self+, + * its trailing elements are ignored: * - * If the size of any argument is less than the size of the initial array, - * +nil+ values are supplied. + * 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 a block is given, it is invoked for each output +array+, otherwise an - * array of arrays is returned. + * When a block is given, calls the block with each of the sub-arrays (formed as above); returns +nil+: + * + * a = [:a0, :a1, :a2, :a3] + * b = [:b0, :b1, :b2, :b3] + * c = [:c0, :c1, :c2, :c3] + * a.zip(b, c) {|sub_array| p sub_array} # => nil + * + * Output: + * + * [:a0, :b0, :c0] + * [:a1, :b1, :c1] + * [:a2, :b2, :c2] + * [:a3, :b3, :c3] * - * 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 @@ -4383,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; @@ -4435,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 @@ -4455,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 content of +self+ with the content of +other_array+; returns +self+: * - * Replaces the contents of +self+ with the contents of +other_ary+, - * truncating or expanding if necessary. + * 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 @@ -4494,31 +4738,35 @@ rb_ary_replace(VALUE copy, VALUE orig) orig = to_ary(orig); if (copy == orig) return copy; - if (RARRAY_LEN(orig) <= RARRAY_EMBED_LEN_MAX) { - VALUE shared_root = 0; + rb_ary_reset(copy); - if (ARY_OWNS_HEAP_P(copy)) { - ary_heap_free(copy); - } - else if (ARY_SHARED_P(copy)) { - shared_root = ARY_SHARED_ROOT(copy); - FL_UNSET_SHARED(copy); - } - FL_SET_EMBED(copy); + /* orig has enough space to embed the contents of orig. */ + if (RARRAY_LEN(orig) <= ary_embed_capa(copy)) { + assert(ARY_EMBED_P(copy)); ary_memcpy(copy, 0, RARRAY_LEN(orig), RARRAY_CONST_PTR_TRANSIENT(orig)); - if (shared_root) { - rb_ary_decrement_share(shared_root); - } - ARY_SET_LEN(copy, RARRAY_LEN(orig)); + ARY_SET_EMBED_LEN(copy, RARRAY_LEN(orig)); + } +#if USE_RVARGC + /* orig is embedded but copy does not have enough space to embed the + * contents of orig. */ + else if (ARY_EMBED_P(orig)) { + long len = ARY_EMBED_LEN(orig); + VALUE *ptr = ary_heap_alloc(copy, len); + + FL_UNSET_EMBED(copy); + ARY_SET_PTR(copy, ptr); + ARY_SET_LEN(copy, len); + ARY_SET_CAPA(copy, len); + + // No allocation and exception expected that could leave `copy` in a + // bad state from the edits above. + ary_memcpy(copy, 0, len, RARRAY_CONST_PTR_TRANSIENT(orig)); } +#endif + /* Otherwise, orig is on heap and copy does not have enough space to embed + * the contents of orig. */ else { VALUE shared_root = ary_make_shared(orig); - if (ARY_OWNS_HEAP_P(copy)) { - ary_heap_free(copy); - } - else { - rb_ary_unshare_safe(copy); - } FL_UNSET_EMBED(copy); ARY_SET_PTR(copy, ARY_HEAP_PTR(orig)); ARY_SET_LEN(copy, ARY_HEAP_LEN(orig)); @@ -4530,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 @@ -4543,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); @@ -4561,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 * - * The first three forms set the selected elements of +self+ (which - * may be the entire array) to +obj+. + * Replaces specified elements in +self+ with specified objects; returns +self+. * - * A +start+ of +nil+ is equivalent to zero. + * With argument +obj+ and no block given, replaces all elements with that one object: * - * A +length+ of +nil+ is equivalent to the length of the array. + * a = ['a', 'b', 'c', 'd'] + * a # => ["a", "b", "c", "d"] + * a.fill(:X) # => [:X, :X, :X, :X] * - * 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 arguments +obj+ and \Integer +start+, and no block given, + * replaces elements based on the given start. * - * Negative values of +start+ count from the end of the array, where +-1+ is - * the last element. + * 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: + * + * 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"] + * + * If <tt>range.last</tt> and <tt>range.last</tt> are both negative, + * both count from the end of the array: + * + * a = ['a', 'b', 'c', 'd'] + * a.fill(:X, (-1..-1)) # => ["a", "b", "c", :X] + * a = ['a', 'b', 'c', 'd'] + * a.fill(:X, (-2..-2)) # => ["a", "b", :X, "d"] + * + * With no arguments and a block given, calls the block with each index; + * replaces the corresponding element with the block's return value: + * + * a = ['a', 'b', 'c', 'd'] + * a.fill { |index| "new_#{index}" } # => ["new_0", "new_1", "new_2", "new_3"] + * + * With argument +start+ and a block given, calls the block with each index + * from offset +start+ to the end; replaces the corresponding element + * with the block's return value. + * + * 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 @@ -4596,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 - * - * Concatenation --- Returns a new array built by concatenating the - * two arrays together to produce a third array. + * array + other_array -> new_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" ] + * Returns a new \Array containing all elements of +array+ + * followed by all elements of +other_array+: * - * 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. + * a = [0, 1] + [2, 3] + * a # => [0, 1, 2, 3] * - * See also Array#concat. + * Related: #concat. */ VALUE @@ -4701,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 + * array.concat(*other_arrays) -> self * - * Appends the elements of <code>other_ary</code>s to +self+. + * Adds to +array+ all elements from each \Array in +other_arrays+; returns +self+: * - * [ "a", "b" ].concat( ["c", "d"]) #=> [ "a", "b", "c", "d" ] - * [ "a" ].concat( ["b"], ["c", "d"]) #=> [ "a", "b", "c", "d" ] - * [ "a" ].concat #=> [ "a" ] - * - * 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 @@ -4730,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); @@ -4753,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}" * */ @@ -4777,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; } @@ -4814,22 +5211,17 @@ rb_ary_times(VALUE ary, VALUE times) /* * 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 @@ -4839,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 @@ -4872,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; } @@ -4895,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 * - * 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+. + * 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>: * - * [ "a", "c" ] == [ "a", "c", 7 ] #=> false - * [ "a", "c", 7 ] == [ "a", "c", 7 ] #=> true - * [ "a", "c", 7 ] == [ "a", "d", "f" ] #=> false + * a0 = [:foo, 'bar', 2] + * a1 = [:foo, 'bar', 2.0] + * a1 == a0 # => true + * [] == [] # => true * + * Otherwise, returns +false+. + * + * This method is different from method Array#eql?, + * which compares elements using <tt>Object#eql?</tt>. */ static VALUE @@ -4934,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; @@ -4951,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 @@ -4977,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 @@ -4997,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); @@ -5006,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 @@ -5023,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; } @@ -5038,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; } @@ -5054,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: * - * Comparison --- Returns an integer (+-1+, +0+, or <code>+1</code>) if this - * array is less than, equal to, or greater than +other_ary+. + * [0, 1, 2] <=> [0, 1, 1] # => 1 * - * Each object in each array is compared (using the <=> operator). + * When all results are zero: * - * 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. + * - Returns -1 if +array+ is smaller than +other_array+: * - * 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. + * [0, 1, 2] <=> [0, 1, 2, 3] # => -1 * - * +nil+ is returned if the +other_ary+ is not an array or if the comparison - * of two elements returned +nil+. + * - Returns 1 if +array+ is larger than +other_array+: * - * [ "a", "a", "c" ] <=> [ "a", "b", "c" ] #=> -1 - * [ 1, 2, 3, 4, 5, 6 ] <=> [ 1, 2 ] #=> +1 - * [ 1, 2 ] <=> [ 1, :two ] #=> nil + * [0, 1, 2] <=> [0, 1] # => 1 + * + * - Returns 0 if +array+ and +other_array+ are the same size: + * + * [0, 1, 2] <=> [0, 1, 2] # => 0 * */ @@ -5106,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); @@ -5119,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; } @@ -5148,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; } @@ -5167,35 +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 all - * occurrences of any item that also appear in +other_ary+. The order is - * preserved from the original array. - * - * It compares elements using their #hash and #eql? methods for efficiency. - * - * [ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ] + * array - other_array -> new_array * - * Note that while 1 and 2 were only present once in the array argument, and - * were present twice in the receiver array, all occurrences of each Integer are - * removed in the returned array. + * 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: * - * If you need set-like behavior, see the library class Set. + * [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] * - * See also Array#difference. + * Related: Array#difference. */ -static VALUE +VALUE rb_ary_diff(VALUE ary1, VALUE ary2) { VALUE ary3; @@ -5203,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; @@ -5225,31 +5622,19 @@ rb_ary_diff(VALUE ary1, VALUE ary2) /* * call-seq: - * ary.difference(other_ary1, other_ary2, ...) -> new_ary - * - * Array Difference - * - * Returns a new array that is a copy of the original array, removing all - * occurrences of any item that also appear in +other_ary+. The order is - * preserved from the original array. + * array.difference(*other_arrays) -> new_array * - * It compares elements using their #hash and #eql? methods for efficiency. + * 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: * - * [ 1, 1, 2, 2, 3, 3, 4, 5 ].difference([ 1, 2, 4 ]) #=> [ 3, 3, 5 ] + * [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] * - * Note that while 1 and 2 were only present once in the array argument, and - * were present twice in the receiver array, all occurrences of each Integer are - * removed in the returned array. + * Returns a copy of +self+ if no arguments given. * - * Multiple array arguments can be supplied and all occurrences of any element - * in those supplied arrays that match the receiver will be removed from the - * returned array. - * - * [ 1, 'c', :s, 'yep' ].difference([ 1 ], [ 'a', 'c' ]) #=> [ :s, "yep" ] - * - * If you need set-like behavior, see the library class Set. - * - * See also Array#-. + * Related: Array#-. */ static VALUE @@ -5291,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 * - * Set Intersection --- Returns a new array containing unique elements common to the - * two arrays. The order is preserved from the original 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>: * - * It compares elements using their #hash and #eql? methods for efficiency. + * [0, 1, 2, 3] & [1, 2] # => [1, 2] + * [0, 1, 0, 1] & [0, 1] # => [0, 1] * - * [ 1, 1, 3, 5 ] & [ 3, 2, 1 ] #=> [ 1, 3 ] - * [ 'a', 'b', 'b', 'z' ] & [ 'a', 'b', 'c' ] #=> [ 'a', 'b' ] + * Preserves order from +array+: * - * See also Array#uniq. + * [0, 1, 2] & [3, 2, 1, 0] # => [0, 1, 2] + * + * Related: Array#intersection. */ @@ -5317,23 +5704,23 @@ rb_ary_and(VALUE ary1, VALUE ary2) if (RARRAY_LEN(ary1) == 0 || RARRAY_LEN(ary2) == 0) return ary3; if (RARRAY_LEN(ary1) <= SMALL_ARRAY_LEN && RARRAY_LEN(ary2) <= SMALL_ARRAY_LEN) { - for (i=0; i<RARRAY_LEN(ary1); i++) { - v = RARRAY_AREF(ary1, i); - if (!rb_ary_includes_by_eql(ary2, v)) continue; - if (rb_ary_includes_by_eql(ary3, v)) continue; - rb_ary_push(ary3, v); - } - return ary3; + for (i=0; i<RARRAY_LEN(ary1); i++) { + v = RARRAY_AREF(ary1, i); + if (!rb_ary_includes_by_eql(ary2, v)) continue; + if (rb_ary_includes_by_eql(ary3, v)) continue; + rb_ary_push(ary3, v); + } + return ary3; } hash = ary_make_hash(ary2); for (i=0; i<RARRAY_LEN(ary1); i++) { - v = RARRAY_AREF(ary1, i); - vv = (st_data_t)v; + v = RARRAY_AREF(ary1, i); + vv = (st_data_t)v; if (rb_hash_stlike_delete(hash, &vv, 0)) { - rb_ary_push(ary3, v); - } + rb_ary_push(ary3, v); + } } ary_recycle_hash(hash); @@ -5342,19 +5729,22 @@ rb_ary_and(VALUE ary1, VALUE ary2) /* * call-seq: - * ary.intersection(other_ary1, other_ary2, ...) -> new_ary + * array.intersection(*other_arrays) -> new_array * - * Set Intersection --- Returns a new array containing unique elements common - * to +self+ and <code>other_ary</code>s. Order is preserved from the original - * 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>: * - * It compares elements using their #hash and #eql? methods for efficiency. + * [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] * - * [ 1, 1, 3, 5 ].intersection([ 3, 2, 1 ]) # => [ 1, 3 ] - * [ "a", "b", "z" ].intersection([ "a", "b", "c" ], [ "b" ]) # => [ "b" ] - * [ "a" ].intersection #=> [ "a" ] + * Preserves order from +self+: * - * See also Array#&. + * [0, 1, 2].intersection([2, 1, 0]) # => [0, 1, 2] + * + * Returns a copy of +self+ if no arguments given. + * + * Related: Array#&. */ static VALUE @@ -5403,17 +5793,17 @@ rb_ary_union_hash(VALUE hash, VALUE ary2) /* * call-seq: - * ary | other_ary -> new_ary - * - * Set Union --- Returns a new array by joining +ary+ with +other_ary+, - * excluding any duplicates and preserving the order from the given arrays. + * array | other_array -> new_array * - * It compares elements using their #hash and #eql? methods for efficiency. + * Returns the union of +array+ and \Array +other_array+; + * duplicates are removed; order is preserved; + * items are compared using <tt>eql?</tt>: * - * [ "a", "b", "c" ] | [ "c", "d", "a" ] #=> [ "a", "b", "c", "d" ] - * [ "c", "d", "a" ] | [ "a", "b", "c" ] #=> [ "c", "d", "a", "b" ] + * [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] * - * See also Array#union. + * Related: Array#union. */ static VALUE @@ -5423,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); @@ -5439,18 +5829,18 @@ rb_ary_or(VALUE ary1, VALUE ary2) /* * call-seq: - * ary.union(other_ary1, other_ary2, ...) -> new_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 @@ -5485,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 +true+ if the array and +other_ary+ have at least one element in + * common, otherwise returns +false+: + * + * a = [ 1, 2, 3 ] + * b = [ 3, 4, 5 ] + * c = [ 5, 6, 7 ] + * a.intersect?(b) #=> true + * a.intersect?(c) #=> false + * + */ + +static VALUE +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. * - * Returns the object in _ary_ with the maximum value. The - * first form assumes all objects implement <code><=></code>; - * the second uses the block to return <em>a <=> b</em>. + * 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: * - * ary = %w(albatross dog horse) - * ary.max #=> "horse" - * ary.max {|a, b| a.length <=> b.length} #=> "albatross" + * ['0', '00', '000'].max {|a, b| a.size <=> b.size } # => "000" * - * If the +n+ argument is given, maximum +n+ elements are returned - * as an array. + * 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; @@ -5516,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 one of the following: + * + * - The minimum-valued element from +self+. + * - A new \Array of minimum-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 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. * - * Returns the object in _ary_ with the minimum value. The - * first form assumes all objects implement <code><=></code>; - * the second uses the block to return <em>a <=> b</em>. + * 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: * - * ary = %w(albatross dog horse) - * ary.min #=> "albatross" - * ary.min {|a, b| a.length <=> b.length} #=> "dog" + * ['0', '00', '000'].min { |a, b| a.size <=> b.size } # => "0" * - * If the +n+ argument is given, minimum +n+ elements are returned - * as an array. + * 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; @@ -5569,36 +6235,58 @@ 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: - * ary.minmax -> [obj, obj] - * ary.minmax {| a,b | block } -> [obj, obj] + * 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:. * - * Returns a two element array which contains the minimum and the - * maximum value in the array. + * 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"] * - * Can be given an optional block to override the default comparison - * method <code>a <=> b</code>. */ static VALUE rb_ary_minmax(VALUE ary) @@ -5618,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) { @@ -5653,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); @@ -5676,22 +6366,24 @@ rb_ary_uniq_bang(VALUE ary) /* * call-seq: - * ary.uniq -> new_ary - * ary.uniq {|item| ...} -> new_ary + * array.uniq -> new_array + * array.uniq {|element| ... } -> new_array * - * Returns a new array by removing duplicate values in +self+. + * Returns a new \Array containing those elements from +self+ that are not duplicates, + * the first occurrence always being retained. * - * If a block is given, it will use the return value of the block for comparison. + * With no block given, identifies and omits duplicates using method <tt>eql?</tt> + * to compare: * - * It compares values using their #hash and #eql? methods for efficiency. + * a = [0, 0, 1, 1, 2, 2] + * a.uniq # => [0, 1, 2] * - * +self+ is traversed in order, and the first occurrence is kept. + * With a block given, calls the block for each element; + * identifies (using method <tt>eql?</tt>) and omits duplicate values, + * that is, those elements for which the block returns the same value: * - * a = [ "a", "a", "b", "b", "c" ] - * a.uniq # => ["a", "b", "c"] - * - * 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"] * */ @@ -5705,14 +6397,13 @@ rb_ary_uniq(VALUE ary) uniq = rb_ary_dup(ary); } else if (rb_block_given_p()) { - hash = ary_make_hash_by(ary); - uniq = rb_hash_values(hash); + hash = ary_make_hash_by(ary); + uniq = rb_hash_values(hash); } else { - hash = ary_make_hash(ary); - uniq = rb_hash_values(hash); + hash = ary_make_hash(ary); + uniq = rb_hash_values(hash); } - RBASIC_SET_CLASS(uniq, rb_obj_class(ary)); if (hash) { ary_recycle_hash(hash); } @@ -5722,14 +6413,11 @@ rb_ary_uniq(VALUE ary) /* * 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 @@ -5743,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); @@ -5757,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 @@ -5775,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 @@ -5800,25 +6494,25 @@ rb_ary_count(int argc, VALUE *argv, VALUE ary) long i, n = 0; if (rb_check_arity(argc, 0, 1) == 0) { - VALUE v; + VALUE v; - if (!rb_block_given_p()) - return LONG2NUM(RARRAY_LEN(ary)); + if (!rb_block_given_p()) + return LONG2NUM(RARRAY_LEN(ary)); - for (i = 0; i < RARRAY_LEN(ary); i++) { - v = RARRAY_AREF(ary, i); - if (RTEST(rb_yield(v))) n++; - } + for (i = 0; i < RARRAY_LEN(ary); i++) { + v = RARRAY_AREF(ary, i); + if (RTEST(rb_yield(v))) n++; + } } else { VALUE obj = argv[0]; - if (rb_block_given_p()) { - rb_warn("given block not used"); - } - for (i = 0; i < RARRAY_LEN(ary); i++) { - if (rb_equal(RARRAY_AREF(ary, i), obj)) n++; - } + if (rb_block_given_p()) { + rb_warn("given block not used"); + } + for (i = 0; i < RARRAY_LEN(ary); i++) { + if (rb_equal(RARRAY_AREF(ary, i), obj)) n++; + } } return LONG2NUM(n); @@ -5829,7 +6523,7 @@ flatten(VALUE ary, int level) { long i; VALUE stack, result, tmp = 0, elt, vmemo; - st_table *memo; + st_table *memo = 0; st_data_t id; for (i = 0; i < RARRAY_LEN(ary); i++) { @@ -5841,8 +6535,6 @@ flatten(VALUE ary, int level) } if (i == RARRAY_LEN(ary)) { return ary; - } else if (tmp == ary) { - rb_raise(rb_eArgError, "tried to flatten recursive array"); } result = ary_new(0, RARRAY_LEN(ary)); @@ -5853,79 +6545,100 @@ flatten(VALUE ary, int level) rb_ary_push(stack, ary); rb_ary_push(stack, LONG2NUM(i + 1)); - 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); + 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_GC_GUARD(vmemo); - st_clear(memo); - rb_raise(rb_eRuntimeError, "flatten reentered"); - } - if (NIL_P(tmp)) { - rb_ary_push(result, elt); - } - else { - id = (st_data_t)tmp; - if (st_is_member(memo, id)) { + 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_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_clear(memo); - - RBASIC_SET_CLASS(result, rb_obj_class(ary)); + } + 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 @@ -5941,7 +6654,7 @@ rb_ary_flatten_bang(int argc, VALUE *argv, VALUE ary) result = flatten(ary, level); if (result == ary) { - return Qnil; + return Qnil; } if (!(mod = ARY_EMBED_P(result))) rb_obj_freeze(result); rb_ary_replace(ary, result); @@ -5952,24 +6665,35 @@ 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). + * 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] * - * 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. - * - * 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 @@ -6001,16 +6725,16 @@ rb_ary_shuffle_bang(rb_execution_context_t *ec, VALUE ary, VALUE randgen) rb_ary_modify(ary); i = len = RARRAY_LEN(ary); RARRAY_PTR_USE(ary, ptr, { - while (i) { - long j = RAND_UPTO(i); - VALUE tmp; + while (i) { + long j = RAND_UPTO(i); + VALUE tmp; if (len != RARRAY_LEN(ary) || ptr != RARRAY_CONST_PTR_TRANSIENT(ary)) { rb_raise(rb_eRuntimeError, "modified during shuffle"); - } - tmp = ptr[--i]; - ptr[i] = ptr[j]; - ptr[j] = tmp; - } + } + tmp = ptr[--i]; + ptr[i] = ptr[j]; + ptr[j] = tmp; + } }); /* WB: no new reference */ return ary; } @@ -6024,7 +6748,7 @@ rb_ary_shuffle(rb_execution_context_t *ec, VALUE ary, VALUE randgen) } static VALUE -rb_ary_sample(rb_execution_context_t *ec, VALUE ary, VALUE randgen, VALUE nv, VALUE to_array) +ary_sample(rb_execution_context_t *ec, VALUE ary, VALUE randgen, VALUE nv, VALUE to_array) { VALUE result; long n, len, i, j, k, idx[10]; @@ -6033,120 +6757,120 @@ rb_ary_sample(rb_execution_context_t *ec, VALUE ary, VALUE randgen, VALUE nv, VA len = RARRAY_LEN(ary); if (!to_array) { - if (len < 2) - i = 0; - else - i = RAND_UPTO(len); + if (len < 2) + i = 0; + else + i = RAND_UPTO(len); - return rb_ary_elt(ary, i); + return rb_ary_elt(ary, i); } n = NUM2LONG(nv); if (n < 0) rb_raise(rb_eArgError, "negative sample number"); if (n > len) n = len; if (n <= numberof(idx)) { - for (i = 0; i < n; ++i) { - rnds[i] = RAND_UPTO(len - i); - } + for (i = 0; i < n; ++i) { + rnds[i] = RAND_UPTO(len - i); + } } k = len; len = RARRAY_LEN(ary); if (len < k && n <= numberof(idx)) { - for (i = 0; i < n; ++i) { - if (rnds[i] >= len) return rb_ary_new_capa(0); - } + for (i = 0; i < n; ++i) { + if (rnds[i] >= len) return rb_ary_new_capa(0); + } } if (n > len) n = len; switch (n) { case 0: - return rb_ary_new_capa(0); + return rb_ary_new_capa(0); case 1: - i = rnds[0]; - return rb_ary_new_from_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); @@ -6154,15 +6878,21 @@ rb_ary_sample(rb_execution_context_t *ec, VALUE ary, VALUE randgen, VALUE nv, VA } 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); @@ -6171,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 * - * Calls the given block for each element +n+ times or forever if +nil+ is - * given. + * 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+: * - * Does nothing if a non-positive number is given or the array is empty. + * output = [] + * [0, 1].cycle(2) {|element| output.push(element) } # => nil + * output # => [0, 1, 0, 1] * - * Returns +nil+ if the loop has finished without getting interrupted. + * If +count+ is zero or negative, does not call the block: * - * If no block is given, an Enumerator is returned instead. + * [0, 1].cycle(0) {|element| fail 'Cannot happen' } # => nil + * [0, 1].cycle(-1) {|element| fail 'Cannot happen' } # => nil * - * 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. + * When a block is given, and argument is omitted or +nil+, cycles forever: + * + * # Prints 0 and 1 forever. + * [0, 1].cycle {|element| puts element } + * [0, 1].cycle(nil) {|element| puts element } + * + * 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) { @@ -6213,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. @@ -6251,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; } @@ -6307,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; } @@ -6334,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+. * - * When invoked with a block, yield all permutations of length +n+ of the - * elements of the array, then return the array itself. + * Example: * - * If +n+ is not specified, yield all permutations of all elements. + * a = [0, 1, 2] + * a.permutation(2) {|permutation| p permutation } * - * The implementation makes no guarantees about the order in which the - * permutations are yielded. + * 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: * - * If no block is given, an Enumerator is returned instead. + * [] * - * Examples: + * When +n+ is out of range (negative or larger than <tt>self.size</tt>), + * does not call the block: + * + * a = [0, 1, 2] + * a.permutation(-1) {|permutation| fail 'Cannot happen' } + * a.permutation(4) {|permutation| fail 'Cannot happen' } + * + * When a block given but no argument, + * behaves the same as <tt>a.permutation(a.size)</tt>: + * + * a = [0, 1, 2] + * a.permutation {|permutation| p permutation } + * + * Output: + * + * [0, 1, 2] + * [0, 2, 1] + * [1, 0, 2] + * [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 @@ -6372,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; } @@ -6406,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); } } @@ -6430,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 * - * When invoked with a block, yields all combinations of length +n+ of elements - * from the array and then returns the array itself. + * Calls the block, if given, with combinations of elements of +self+; + * returns +self+. The order of combinations is indeterminate. * - * The implementation makes no guarantees about the order in which the - * combinations are yielded. + * 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+. * - * If no block is given, an Enumerator is returned instead. + * Example: * - * Examples: + * a = [0, 1, 2] + * a.combination(2) {|combination| p combination } * - * 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 + * Output: + * + * [0, 1] + * [0, 2] + * [1, 2] + * + * Another example: + * + * a = [0, 1, 2] + * a.combination(3) {|combination| p combination } + * + * Output: + * + * [0, 1, 2] + * + * When +n+ is zero, calls the block once with a new empty \Array: + * + * a = [0, 1, 2] + * a1 = a.combination(0) {|combination| p combination } + * + * Output: + * + * [] + * + * When +n+ is out of range (negative or larger than <tt>self.size</tt>), + * does not call the block: + * + * a = [0, 1, 2] + * a.combination(-1) {|combination| fail 'Cannot happen' } + * a.combination(4) {|combination| fail 'Cannot happen' } + * + * Returns a new \Enumerator if no block given: + * + * a = [0, 1, 2] + * a.combination(2) # => #<Enumerator: [0, 1, 2]:combination(2)> * */ @@ -6462,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; } @@ -6504,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); } } @@ -6527,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 invoked with a block, yield all repeated permutations of length +n+ of - * the elements of the array, then return the array itself. + * 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>. * - * The implementation makes no guarantees about the order in which the repeated - * permutations are yielded. + * +n+ = 1: * - * If no block is given, an Enumerator is returned instead. + * a = [0, 1, 2] + * a.repeated_permutation(1) {|permutation| p permutation } * - * Examples: + * 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] + * + * If +n+ is zero, calls the block once with an empty \Array. + * + * If +n+ is negative, does not call the block: + * + * a.repeated_permutation(-1) {|permutation| fail 'Cannot happen' } + * + * Returns a new \Enumerator if no block given: + * + * a = [0, 1, 2] + * a.repeated_permutation(2) # => #<Enumerator: [0, 1, 2]:permutation(2)> + * + * Using Enumerators, it's convenient to show the permutations and counts + * for some values of +n+: + * + * e = a.repeated_permutation(0) + * e.size # => 1 + * e.to_a # => [[]] + * 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) { @@ -6568,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; } @@ -6598,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); } } @@ -6620,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 invoked with a block, yields all repeated combinations of length +n+ of - * elements from the array and then returns the array itself. + * 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>. * - * The implementation makes no guarantees about the order in which the repeated - * combinations are yielded. + * +n+ = 1: * - * If no block is given, an Enumerator is returned instead. + * a = [0, 1, 2] + * a.repeated_combination(1) {|combination| p combination } + * + * Output: * - * Examples: + * [0] + * [1] + * [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 + * +n+ = 2: + * + * 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]] * */ @@ -6661,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]] * - * Returns an array of all combinations of elements from all arrays. + * If any argument is an empty \Array, returns an empty \Array. * - * The length of the returned array is the product of the length of +self+ and - * the argument arrays. + * 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: * - * If given a block, #product will yield all combinations and return +self+ - * instead. + * [0, 3] + * [0, 4] + * [1, 3] + * [1, 4] + * [2, 3] + * [2, 4] + * + * If any argument is an empty \Array, does not call the block: + * + * a.product(a1, a2, []) {|combination| fail 'Cannot happen' } + * + * If no argument is given, yields each element of +self+ as a 1-element \Array: + * + * a.product {|combination| p combination } + * + * Output: + * + * [0] + * [1] + * [2] * - * [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 */ @@ -6733,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; @@ -6798,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] * */ @@ -6816,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 + * + * Returns a new \Array containing zero or more leading elements of +self+; + * does not modify +self+. * - * Passes elements to the block until the block returns +nil+ or +false+, then - * stops iterating and returns an array of all prior 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 containing 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.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] * - * See also Array#drop_while + * With no block given, returns a new \Enumerator: * - * a = [1, 2, 3, 4, 5, 0] - * a.take_while {|i| i < 3} #=> [1, 2] + * [0, 1].take_while # => #<Enumerator: [0, 1]:take_while> * */ @@ -6845,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 + * array.drop(n) -> new_array * - * Drops first +n+ elements from +ary+ and returns the rest of the elements in - * an array. + * Returns a new \Array containing all but the first +n+ element of +self+, + * where +n+ is a non-negative \Integer; + * does not modify +self+. * - * If a negative number is given, raises an ArgumentError. + * Examples: * - * See also Array#take - * - * 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] * */ @@ -6872,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> * */ @@ -6905,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 @@ -6929,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) { @@ -6939,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: * - * See also Enumerable#all? + * [0, 1, :foo].all? # => true + * [0, nil, 2].all? # => false + * [].all? # => true + * + * With a block given and no argument, calls the block with each element in +self+; + * returns +true+ if the block returns only truthy values, +false+ otherwise: + * + * [0, 1, 2].all? { |element| element < 3 } # => true + * [0, 1, 2].all? { |element| element < 2 } # => false + * + * If argument +obj+ is given, returns +true+ if <tt>obj.===</tt> every element, +false+ otherwise: + * + * ['food', 'fool', 'foot'].all?(/foo/) # => true + * ['food', 'drink'].all?(/bar/) # => false + * [].all?(/foo/) # => true + * [0, 0, 0].all?(0) # => true + * [0, 1, 2].all?(1) # => false + * + * Related: Enumerable#all? */ static VALUE @@ -6984,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: + * + * [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 * - * See also Enumerable#none? + * 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 @@ -7020,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 @@ -7065,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 @@ -7095,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); @@ -7112,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#+. * */ @@ -7179,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); @@ -7199,7 +8246,7 @@ 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; @@ -7216,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); @@ -7269,75 +8316,128 @@ rb_ary_deconstruct(VALUE ary) } /* - * An \Array is an ordered, integer-indexed collection of objects, - * called _elements_. Any object may be an \Array element. + * An \Array is an ordered, integer-indexed collection of objects, called _elements_. + * Any object (even another array) may be an array element, + * and an array can contain objects of different types. * * == \Array Indexes * * \Array indexing starts at 0, as in C or Java. * * A positive index is an offset from the first element: + * * - Index 0 indicates the first element. * - Index 1 indicates the second element. * - ... * * A negative index is an offset, backwards, from the end of the array: + * * - Index -1 indicates the last element. * - Index -2 indicates the next-to-last element. * - ... * - * A non-negative index is <i>in range</i> if it is smaller than + * A non-negative index is <i>in range</i> if and only if it is smaller than * the size of the array. For a 3-element array: + * * - Indexes 0 through 2 are in range. * - Index 3 is out of range. * - * A negative index is <i>in range</i> if its absolute value is + * A negative index is <i>in range</i> if and only if its absolute value is * not larger than the size of the array. For a 3-element array: + * * - Indexes -1 through -3 are in range. * - Index -4 is out of range. * + * 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]. + * + * * == 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. @@ -7385,7 +8485,7 @@ rb_ary_deconstruct(VALUE ary) * * arr.drop(3) #=> [4, 5, 6] * - * == Obtaining Information about an Array + * == Obtaining Information about an \Array * * Arrays keep track of their own length at all times. To query an array * about the number of elements it contains, use #length, #count or #size. @@ -7423,7 +8523,7 @@ rb_ary_deconstruct(VALUE ary) * arr.insert(3, 'orange', 'pear', 'grapefruit') * #=> [0, 1, 2, "orange", "pear", "grapefruit", "apple", 3, 4, 5, 6] * - * == Removing Items from an Array + * == Removing Items from an \Array * * The method #pop removes the last element in an array and returns it: * @@ -7465,9 +8565,9 @@ rb_ary_deconstruct(VALUE ary) * * == Iterating over Arrays * - * Like all classes that include the Enumerable module, Array has an each + * Like all classes that include the Enumerable module, \Array has an each * method, which defines what elements should be iterated over and how. In - * case of Array's #each, all elements in the Array instance are yielded to + * case of Array's #each, all elements in the \Array instance are yielded to * the supplied block in sequence. * * Note that this operation leaves the array unchanged. @@ -7493,7 +8593,8 @@ rb_ary_deconstruct(VALUE ary) * arr.map! {|a| a**2} #=> [1, 4, 9, 16, 25] * arr #=> [1, 4, 9, 16, 25] * - * == Selecting Items from an Array + * + * == Selecting Items from an \Array * * Elements can be selected from an array according to criteria defined in a * block. The selection can happen in a destructive or a non-destructive @@ -7523,18 +8624,199 @@ rb_ary_deconstruct(VALUE ary) * arr = [1, 2, 3, 4, 5, 6] * 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); @@ -7560,6 +8842,7 @@ Init_Array(void) 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"); @@ -7572,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); @@ -1,62 +1,69 @@ class Array # call-seq: - # ary.shuffle! -> ary - # ary.shuffle!(random: rng) -> ary + # array.shuffle!(random: Random) -> array # - # 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. + # 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) - __builtin_rb_ary_shuffle_bang(random) + Primitive.rb_ary_shuffle_bang(random) end # call-seq: - # ary.shuffle -> new_ary - # ary.shuffle(random: rng) -> new_ary + # 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] # - # 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. - # + # 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) - __builtin_rb_ary_shuffle(random) + Primitive.rb_ary_shuffle(random) end # 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. + # array.sample(random: Random) -> object + # array.sample(n, random: Random) -> new_ary # - # If the array is empty the first form returns +nil+ and the second form - # returns an empty array. + # Returns random elements from +self+. # - # a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] - # a.sample #=> 7 - # a.sample(4) #=> [6, 4, 2, 5] + # 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+. # - # The optional +rng+ argument will be used as the random number generator. + # 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) - __builtin_rb_ary_sample(random, n, ary) + 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 @@ -17,7 +17,7 @@ static VALUE rb_cNode; struct ASTNodeData { rb_ast_t *ast; - NODE *node; + const NODE *node; }; static void @@ -44,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; @@ -54,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; @@ -64,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) @@ -86,29 +85,33 @@ ast_parse_done(rb_ast_t *ast) } static VALUE -ast_s_parse(rb_execution_context_t *ec, 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); } static VALUE -ast_s_parse_file(rb_execution_context_t *ec, 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; @@ -117,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); } @@ -136,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) @@ -188,35 +199,69 @@ script_lines(VALUE path) } static VALUE -ast_s_of(rb_execution_context_t *ec, 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; - 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) && rb_iseq_from_eval_p(iseq) && !e_option) { + rb_raise(rb_eArgError, "cannot get AST for method defined in eval"); } - else if (RSTRING_LEN(path) == 2 && memcmp(RSTRING_PTR(path), "-e", 2) == 0) { - node = rb_ast_parse_str(rb_e_script); + + 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 (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); @@ -246,6 +291,15 @@ ast_node_type(rb_execution_context_t *ec, 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 @@ -268,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)); @@ -284,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_LIST) { + 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)); } @@ -307,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 '$' */ @@ -330,22 +398,17 @@ node_children(rb_ast_t *ast, NODE *node) 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_push(rb_ary_new_from_node_args(ast, 2, node->nd_cond, node->nd_body), - (node->nd_state ? Qtrue : Qfalse)); + 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); @@ -360,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; } @@ -382,24 +443,17 @@ node_children(rb_ast_t *ast, NODE *node) else { return rb_ary_new_from_args(3, NEW_CHILD(ast, node->nd_value), NEW_CHILD(ast, node->nd_head), - ID2SYM(rb_intern("NODE_SPECIAL_NO_NAME_REST"))); + 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(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)); @@ -411,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), @@ -441,9 +496,7 @@ node_children(rb_ast_t *ast, NODE *node) case NODE_ZSUPER: return rb_ary_new_from_node_args(ast, 0); case NODE_LIST: - goto ary; case NODE_VALUES: - ary: return dump_array(ast, node); case NODE_ZLIST: return rb_ary_new_from_node_args(ast, 0); @@ -467,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); @@ -476,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: @@ -527,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); @@ -561,7 +608,7 @@ 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_args(2, ID2SYM(rb_intern("NODE_SPECIAL_NO_NAME_REST")), + return rb_ary_new_from_args(2, no_name_rest(), NEW_CHILD(ast, node->nd_2nd)); case NODE_ARGS: { @@ -573,26 +620,27 @@ 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), + (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 = NODE_NAMED_REST_P(apinfo->rest_arg) ? NEW_CHILD(ast, apinfo->rest_arg) : - ID2SYM(rb_intern("NODE_SPECIAL_NO_NAME_REST")); + 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), @@ -602,10 +650,8 @@ node_children(rb_ast_t *ast, NODE *node) case NODE_FNDPTN: { struct rb_fnd_pattern_info *fpinfo = node->nd_fpinfo; - VALUE pre_rest = NODE_NAMED_REST_P(fpinfo->pre_rest_arg) ? NEW_CHILD(ast, fpinfo->pre_rest_arg) : - ID2SYM(rb_intern("NODE_SPECIAL_NO_NAME_REST")); - VALUE post_rest = NODE_NAMED_REST_P(fpinfo->post_rest_arg) ? NEW_CHILD(ast, fpinfo->post_rest_arg) : - ID2SYM(rb_intern("NODE_SPECIAL_NO_NAME_REST")); + 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, @@ -622,6 +668,8 @@ node_children(rb_ast_t *ast, NODE *node) NEW_CHILD(ast, node->nd_pkwargs), kwrest); } + case NODE_ERROR: + return rb_ary_new_from_node_args(ast, 0); case NODE_ARGS_AUX: case NODE_LAST: break; @@ -676,6 +724,15 @@ ast_node_last_column(rb_execution_context_t *ec, VALUE self) } static VALUE +ast_node_all_tokens(rb_execution_context_t *ec, VALUE self) +{ + struct ASTNodeData *data; + TypedData_Get_Struct(self, struct ASTNodeData, &rb_node_type, data); + + return rb_ast_tokens(data->ast); +} + +static VALUE ast_node_inspect(rb_execution_context_t *ec, VALUE self) { VALUE str; @@ -695,6 +752,16 @@ ast_node_inspect(rb_execution_context_t *ec, 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 @@ -1,145 +1,275 @@ # for ast.c -class RubyVM +# 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 - # AbstractSyntaxTree provides methods to parse Ruby code into - # abstract syntax trees. The nodes in the tree - # are instances of RubyVM::AbstractSyntaxTree::Node. + # call-seq: + # RubyVM::AbstractSyntaxTree.parse(string, keep_script_lines: false, error_tolerant: false, keep_tokens: false) -> RubyVM::AbstractSyntaxTree::Node # - # This class is MRI specific as it exposes implementation details - # of the MRI abstract syntax tree. + # Parses the given _string_ into an abstract syntax tree, + # returning the root node of that tree. # - # This class 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. + # RubyVM::AbstractSyntaxTree.parse("x = 1 + 2") + # # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-1:9> # - # 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. + # 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. # - module AbstractSyntaxTree + # 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: - # RubyVM::AbstractSyntaxTree.parse(string) -> RubyVM::AbstractSyntaxTree::Node + # node.type -> symbol # - # Parses the given _string_ into an abstract syntax tree, - # returning the root node of that tree. + # Returns the type of this node as a symbol. # - # SyntaxError is raised if the given _string_ is invalid syntax. + # 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 # - # RubyVM::AbstractSyntaxTree.parse("x = 1 + 2") - # # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-1:9> - def self.parse string - __builtin_ast_s_parse string + # The line number in the source code where this AST's text began. + def first_lineno + Primitive.ast_node_first_lineno end # call-seq: - # RubyVM::AbstractSyntaxTree.parse_file(pathname) -> RubyVM::AbstractSyntaxTree::Node + # node.first_column -> integer # - # Reads the file from _pathname_, then parses it like ::parse, - # returning the root node of the abstract syntax tree. + # 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 # - # SyntaxError is raised if _pathname_'s contents are not - # valid Ruby syntax. + # 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 # - # RubyVM::AbstractSyntaxTree.parse_file("my-app/app.rb") - # # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-31:3> - def self.parse_file pathname - __builtin_ast_s_parse_file pathname + # The column number in the source code where this AST's text ended. + def last_column + Primitive.ast_node_last_column end # call-seq: - # RubyVM::AbstractSyntaxTree.of(proc) -> RubyVM::AbstractSyntaxTree::Node - # RubyVM::AbstractSyntaxTree.of(method) -> RubyVM::AbstractSyntaxTree::Node + # node.tokens -> array # - # Returns AST nodes of the given _proc_ or _method_. + # Returns tokens corresponding to the location of the node. + # Returns +nil+ if +keep_tokens+ is not enabled when #parse method is called. # - # RubyVM::AbstractSyntaxTree.of(proc {1 + 2}) - # # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:35-1:42> + # 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" # - # def hello - # puts "hello, world" - # end + # Token is an array of: # - # RubyVM::AbstractSyntaxTree.of(method(:hello)) - # # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-3:3> - def self.of body - __builtin_ast_s_of body - end + # - id + # - token type + # - source code text + # - location [ first_lineno, first_column, last_lineno, last_column ] + def tokens + return nil unless all_tokens - # 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 - __builtin_ast_node_type + 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.first_lineno -> integer - # - # The line number in the source code where this AST's text began. - def first_lineno - __builtin_ast_node_first_lineno - 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.first_column -> integer - # - # The column number in the source code where this AST's text began. - def first_column - __builtin_ast_node_first_column - 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.last_lineno -> integer - # - # The line number in the source code where this AST's text ended. - def last_lineno - __builtin_ast_node_last_lineno - 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.last_column -> integer - # - # The column number in the source code where this AST's text ended. - def last_column - __builtin_ast_node_last_column - 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.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 - __builtin_ast_node_children - 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.inspect -> string - # - # Returns debugging information about this node as a string. - def inspect - __builtin_ast_node_inspect + # 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 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 52008b78db..95875b52a6 100755 --- a/basictest/test.rb +++ b/basictest/test.rb @@ -1960,6 +1960,8 @@ test_ok(p1.call == 5) test_ok(i7 == nil) end +# WASI doesn't support spawning a new process for now. +unless /wasi/ =~ RUBY_PLATFORM test_check "system" test_ok(`echo foobar` == "foobar\n") test_ok(`./miniruby -e 'print "foobar"'` == 'foobar') @@ -2010,6 +2012,7 @@ test_ok(done) File.unlink script_tmp or `/bin/rm -f "#{script_tmp}"` File.unlink "#{script_tmp}.bak" or `/bin/rm -f "#{script_tmp}.bak"` +end # not /wasi/ =~ RUBY_PLATFORM test_check "const" TEST1 = 1 diff --git a/benchmark/README.md b/benchmark/README.md index 39a5aa139b..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) ``` 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 index af6abd08ac..655be9d7d8 100644 --- a/benchmark/cgi_escape_html.yml +++ b/benchmark/cgi_escape_html.yml @@ -1,32 +1,23 @@ -prelude: require 'cgi/escape' +prelude: | + # frozen_string_literal: true + require 'cgi/escape' benchmark: - - name: escape_html_blank - prelude: str = "" - script: CGI.escapeHTML(str) + - script: CGI.escapeHTML("") loop_count: 20000000 - - name: escape_html_short_none - prelude: str = "abcde" - script: CGI.escapeHTML(str) + - script: CGI.escapeHTML("abcde") loop_count: 20000000 - - name: escape_html_short_one - prelude: str = "abcd<" - script: CGI.escapeHTML(str) + - script: CGI.escapeHTML("abcd<") loop_count: 20000000 - - name: escape_html_short_all - prelude: str = "'&\"<>" - script: CGI.escapeHTML(str) + - script: CGI.escapeHTML("'&\"<>") loop_count: 5000000 - - name: escape_html_long_none - prelude: str = "abcde" * 300 - script: CGI.escapeHTML(str) + - prelude: long_no_escape = "abcde" * 300 + script: CGI.escapeHTML(long_no_escape) loop_count: 1000000 - - name: escape_html_long_all - prelude: str = "'&\"<>" * 10 - script: CGI.escapeHTML(str) + - prelude: long_all_escape = "'&\"<>" * 10 + script: CGI.escapeHTML(long_all_escape) loop_count: 1000000 - - name: escape_html_real - prelude: | # http://example.com/ - str = <<~HTML + - prelude: | # http://example.com/ + example_html = <<~HTML <body> <div> <h1>Example Domain</h1> @@ -36,5 +27,5 @@ benchmark: </div> </body> HTML - script: CGI.escapeHTML(str) + script: CGI.escapeHTML(example_html) loop_count: 1000000 diff --git a/benchmark/constant_invalidation.rb b/benchmark/constant_invalidation.rb new file mode 100644 index 0000000000..a95ec6f37e --- /dev/null +++ b/benchmark/constant_invalidation.rb @@ -0,0 +1,22 @@ +$VERBOSE = nil + +CONSTANT1 = 1 +CONSTANT2 = 1 +CONSTANT3 = 1 +CONSTANT4 = 1 +CONSTANT5 = 1 + +def constants + [CONSTANT1, CONSTANT2, CONSTANT3, CONSTANT4, CONSTANT5] +end + +500_000.times do + constants + + # With previous behavior, this would cause all of the constant caches + # associated with the constant lookups listed above to invalidate, meaning + # they would all have to be fetched again. With current behavior, it only + # invalidates when a name matches, so the following constant set shouldn't + # impact the constant lookups listed above. + INVALIDATE = true +end diff --git a/benchmark/enum_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/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_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/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_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/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/mjit_exec.rb b/benchmark/lib/benchmark_driver/runner/mjit_exec.rb deleted file mode 100644 index 9f7c8c8af3..0000000000 --- a/benchmark/lib/benchmark_driver/runner/mjit_exec.rb +++ /dev/null @@ -1,237 +0,0 @@ -require 'benchmark_driver/struct' -require 'benchmark_driver/metric' -require 'erb' - -# A special runner dedicated for measuring mjit_exec overhead. -class BenchmarkDriver::Runner::MjitExec - METRIC = BenchmarkDriver::Metric.new(name: 'Iteration per second', unit: 'i/s') - - # JobParser returns this, `BenchmarkDriver::Runner.runner_for` searches "*::Job" - Job = ::BenchmarkDriver::Struct.new( - :name, # @param [String] name - This is mandatory for all runner - :metrics, # @param [Array<BenchmarkDriver::Metric>] - :num_methods, # @param [Integer] num_methods - The number of methods to be defined - :loop_count, # @param [Integer] loop_count - :from_jit, # @param [TrueClass,FalseClass] from_jit - Whether the mjit_exec() is from JIT or not - :to_jit, # @param [TrueClass,FalseClass] to_jit - Whether the mjit_exec() is to JIT or not - ) - # Dynamically fetched and used by `BenchmarkDriver::JobParser.parse` - class << JobParser = Module.new - # @param [Array,String] num_methods - # @param [Integer] loop_count - # @param [TrueClass,FalseClass] from_jit - # @param [TrueClass,FalseClass] to_jit - def parse(num_methods:, loop_count:, from_jit:, to_jit:) - if num_methods.is_a?(String) - num_methods = eval(num_methods) - end - - num_methods.map do |num| - if num_methods.size > 1 - suffix = "[#{'%4d' % num}]" - else - suffix = "_#{num}" - end - Job.new( - name: "mjit_exec_#{from_jit ? 'JT' : 'VM'}2#{to_jit ? 'JT' : 'VM'}#{suffix}", - metrics: [METRIC], - num_methods: num, - loop_count: loop_count, - from_jit: from_jit, - to_jit: to_jit, - ) - end - end - end - - # @param [BenchmarkDriver::Config::RunnerConfig] config - # @param [BenchmarkDriver::Output] output - # @param [BenchmarkDriver::Context] contexts - def initialize(config:, output:, contexts:) - @config = config - @output = output - @contexts = contexts - end - - # This method is dynamically called by `BenchmarkDriver::JobRunner.run` - # @param [Array<BenchmarkDriver::Runner::Peak::Job>] jobs - def run(jobs) - @output.with_benchmark do - jobs.each do |job| - @output.with_job(name: job.name) do - @contexts.each do |context| - result = BenchmarkDriver::Repeater.with_repeat(config: @config, larger_better: true, rest_on_average: :average) do - run_benchmark(job, context: context) - end - value, duration = result.value - @output.with_context(name: context.name, executable: context.executable, gems: context.gems, prelude: context.prelude) do - @output.report(values: { METRIC => value }, duration: duration, loop_count: job.loop_count) - end - end - end - end - end - end - - private - - # @param [BenchmarkDriver::Runner::Ips::Job] job - loop_count is not nil - # @param [BenchmarkDriver::Context] context - # @return [BenchmarkDriver::Metrics] - def run_benchmark(job, context:) - if job.from_jit - if job.to_jit - benchmark = BenchmarkJT2JT.new(num_methods: job.num_methods, loop_count: job.loop_count) - else - raise NotImplementedError, "JT2VM is not implemented yet" - end - else - if job.to_jit - benchmark = BenchmarkVM2JT.new(num_methods: job.num_methods, loop_count: job.loop_count) - else - benchmark = BenchmarkVM2VM.new(num_methods: job.num_methods, loop_count: job.loop_count) - end - end - - duration = Tempfile.open(['benchmark_driver-result', '.txt']) do |f| - with_script(benchmark.render(result: f.path)) do |path| - opt = [] - if context.executable.command.any? { |c| c.start_with?('--jit') } - opt << '--jit-min-calls=2' - end - IO.popen([*context.executable.command, '--disable-gems', *opt, path], &:read) - if $?.success? - Float(f.read) - else - BenchmarkDriver::Result::ERROR - end - end - end - - [job.loop_count.to_f / duration, duration] - end - - def with_script(script) - if @config.verbose >= 2 - sep = '-' * 30 - $stdout.puts "\n\n#{sep}[Script begin]#{sep}\n#{script}#{sep}[Script end]#{sep}\n\n" - end - - Tempfile.open(['benchmark_driver-', '.rb']) do |f| - f.puts script - f.close - return yield(f.path) - end - end - - # @param [Integer] num_methods - # @param [Integer] loop_count - BenchmarkVM2VM = ::BenchmarkDriver::Struct.new(:num_methods, :loop_count) do - # @param [String] result - A file to write result - def render(result:) - ERB.new(<<~EOS, trim_mode: '%').result(binding) - % num_methods.times do |i| - def a<%= i %> - nil - end - % end - RubyVM::MJIT.pause if RubyVM::MJIT.enabled? - - def vm - t = Process.clock_gettime(Process::CLOCK_MONOTONIC) - i = 0 - while i < <%= loop_count / 1000 %> - % 1000.times do |i| - a<%= i % num_methods %> - % end - i += 1 - end - % (loop_count % 1000).times do |i| - a<%= i % num_methods %> - % end - Process.clock_gettime(Process::CLOCK_MONOTONIC) - t - end - - vm # warmup call cache - File.write(<%= result.dump %>, vm) - EOS - end - end - private_constant :BenchmarkVM2VM - - # @param [Integer] num_methods - # @param [Integer] loop_count - BenchmarkVM2JT = ::BenchmarkDriver::Struct.new(:num_methods, :loop_count) do - # @param [String] result - A file to write result - def render(result:) - ERB.new(<<~EOS, trim_mode: '%').result(binding) - % num_methods.times do |i| - def a<%= i %> - nil - end - a<%= i %> - a<%= i %> # --jit-min-calls=2 - % end - RubyVM::MJIT.pause if RubyVM::MJIT.enabled? - - def vm - t = Process.clock_gettime(Process::CLOCK_MONOTONIC) - i = 0 - while i < <%= loop_count / 1000 %> - % 1000.times do |i| - a<%= i % num_methods %> - % end - i += 1 - end - % (loop_count % 1000).times do |i| - a<%= i % num_methods %> - % end - Process.clock_gettime(Process::CLOCK_MONOTONIC) - t - end - - vm # warmup call cache - File.write(<%= result.dump %>, vm) - EOS - end - end - private_constant :BenchmarkVM2JT - - # @param [Integer] num_methods - # @param [Integer] loop_count - BenchmarkJT2JT = ::BenchmarkDriver::Struct.new(:num_methods, :loop_count) do - # @param [String] result - A file to write result - def render(result:) - ERB.new(<<~EOS, trim_mode: '%').result(binding) - % num_methods.times do |i| - def a<%= i %> - nil - end - % end - - # You may need to: - # * Increase `JIT_ISEQ_SIZE_THRESHOLD` to 10000000 in mjit.h - # * Always return false in `inlinable_iseq_p()` of mjit_compile.c - def jit - t = Process.clock_gettime(Process::CLOCK_MONOTONIC) - i = 0 - while i < <%= loop_count / 1000 %> - % 1000.times do |i| - a<%= i % num_methods %> - % end - i += 1 - end - % (loop_count % 1000).times do |i| - a<%= i % num_methods %> - % end - Process.clock_gettime(Process::CLOCK_MONOTONIC) - t - end - - jit - jit - RubyVM::MJIT.pause if RubyVM::MJIT.enabled? - File.write(<%= result.dump %>, jit) - EOS - end - end - private_constant :BenchmarkJT2JT -end 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/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/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_exec_jt2jt.yml b/benchmark/mjit_exec_jt2jt.yml deleted file mode 100644 index 5be408e30c..0000000000 --- a/benchmark/mjit_exec_jt2jt.yml +++ /dev/null @@ -1,8 +0,0 @@ -# Usage: -# RUBYOPT=-Ibenchmark/lib benchmark-driver -e 'ruby --jit' benchmark/mjit_exec_vm2jt.yml -type: mjit_exec # benchmark/lib/benchmark_driver/runner/mjit_exec.rb -num_methods: [1] -#num_methods: (1..100).to_a + [200, 300, 400, 500, 600, 700, 800, 900, 1000] -loop_count: 50000000 -from_jit: true -to_jit: true diff --git a/benchmark/mjit_exec_vm2jt.yml b/benchmark/mjit_exec_vm2jt.yml deleted file mode 100644 index 9947dbb7dd..0000000000 --- a/benchmark/mjit_exec_vm2jt.yml +++ /dev/null @@ -1,8 +0,0 @@ -# Usage: -# RUBYOPT=-Ibenchmark/lib benchmark-driver -e 'ruby --jit' benchmark/mjit_exec_vm2jt.yml -type: mjit_exec # benchmark/lib/benchmark_driver/runner/mjit_exec.rb -num_methods: [1] -#num_methods: (1..100).to_a + [200, 300, 400, 500, 600, 700, 800, 900, 1000] -loop_count: 50000000 -from_jit: false -to_jit: true diff --git a/benchmark/mjit_exec_vm2vm.yml b/benchmark/mjit_exec_vm2vm.yml deleted file mode 100644 index 4b84427b10..0000000000 --- a/benchmark/mjit_exec_vm2vm.yml +++ /dev/null @@ -1,8 +0,0 @@ -# Usage: -# RUBYOPT=-Ibenchmark/lib benchmark-driver -e 'ruby --jit' benchmark/mjit_exec_vm2vm.yml -type: mjit_exec # benchmark/lib/benchmark_driver/runner/mjit_exec.rb -num_methods: [1] -#num_methods: (1..100).to_a + [200, 300, 400, 500, 600, 700, 800, 900, 1000] -loop_count: 50000000 -from_jit: false -to_jit: false diff --git a/benchmark/mjit_exivar.yml b/benchmark/mjit_exivar.yml index e6fbae96de..2584fa6410 100644 --- a/benchmark/mjit_exivar.yml +++ b/benchmark/mjit_exivar.yml @@ -1,3 +1,4 @@ +type: lib/benchmark_driver/runner/mjit prelude: | class Bench < Hash def initialize @@ -11,20 +12,6 @@ prelude: | bench = Bench.new - if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? - jit_min_calls = 10000 - i = 0 - while i < jit_min_calls - bench.exivar - i += 1 - end - RubyVM::MJIT.pause # compile (1) - # issue recompile - bench.exivar - RubyVM::MJIT.resume - RubyVM::MJIT.pause # compile (2) - end - benchmark: mjit_exivar: bench.exivar 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 index 292d6ef041..9ac68b164b 100644 --- a/benchmark/mjit_leave.yml +++ b/benchmark/mjit_leave.yml @@ -1,3 +1,4 @@ +type: lib/benchmark_driver/runner/mjit prelude: | def leave nil 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_send_cfunc.yml b/benchmark/mjit_send_cfunc.yml deleted file mode 100644 index b5f9c897ec..0000000000 --- a/benchmark/mjit_send_cfunc.yml +++ /dev/null @@ -1,6 +0,0 @@ -prelude: | - def mjit_send_cfunc - self.class - end -benchmark: mjit_send_cfunc -loop_count: 100000000 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/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/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_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/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/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/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/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_const.yml b/benchmark/vm_const.yml index 6064d4eed0..8939ca0cd3 100644 --- a/benchmark/vm_const.yml +++ b/benchmark/vm_const.yml @@ -1,7 +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_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_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_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_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_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_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_send.yml b/benchmark/vm_send.yml index 753d3e8318..f31bc7ac89 100644 --- a/benchmark/vm_send.yml +++ b/benchmark/vm_send.yml @@ -5,7 +5,10 @@ prelude: | 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_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 @@ -23,8 +23,14 @@ # include <ieeefp.h> #endif +#if !defined(USE_GMP) #if defined(HAVE_LIBGMP) && defined(HAVE_GMP_H) -# define USE_GMP +# define USE_GMP 1 +#else +# define USE_GMP 0 +#endif +#endif +#if USE_GMP # include <gmp.h> #endif @@ -36,15 +42,12 @@ #include "internal/numeric.h" #include "internal/object.h" #include "internal/sanitizers.h" -#include "internal/util.h" #include "internal/variable.h" #include "internal/warnings.h" #include "ruby/thread.h" #include "ruby/util.h" #include "ruby_assert.h" -#define RB_BIGNUM_TYPE_P(x) RB_TYPE_P((x), T_BIGNUM) - const char ruby_digitmap[] = "0123456789abcdefghijklmnopqrstuvwxyz"; #ifndef SIZEOF_BDIGIT_DBL @@ -75,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))) @@ -102,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) : \ @@ -148,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 @@ -159,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 @@ -420,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); } @@ -438,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); } } @@ -450,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; } @@ -467,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; @@ -979,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; } @@ -989,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; @@ -1057,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 } @@ -1351,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++) { @@ -1372,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; @@ -1380,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; } @@ -1410,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; @@ -1438,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; } @@ -1580,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) @@ -1598,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 */ @@ -1646,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); @@ -1660,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, @@ -1675,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); @@ -2006,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 @@ -2079,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); @@ -2286,7 +2309,7 @@ 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) { @@ -2359,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; } @@ -2424,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); @@ -2469,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 */ @@ -2492,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 @@ -2547,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); @@ -2571,26 +2598,26 @@ 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; } @@ -2659,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; + 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); } } @@ -2767,7 +2794,7 @@ 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) { @@ -2851,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; @@ -2910,30 +2937,6 @@ bary_divmod(BDIGIT *qds, size_t qn, BDIGIT *rds, size_t rn, const BDIGIT *xds, s # define BIGNUM_DEBUG (0+RUBY_DEBUG) #endif -#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) -#endif - static int bigzero_p(VALUE x) { @@ -2950,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); @@ -2959,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; @@ -2971,8 +2974,8 @@ rb_cmpint(VALUE val, VALUE a, VALUE b) #define BIGNUM_SET_LEN(b,l) \ (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 @@ -2980,33 +2983,33 @@ rb_big_realloc(VALUE big, size_t len) { BDIGIT *ds; 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; + 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; + if (len <= BIGNUM_EMBED_LEN_MAX) { + ds = RBIGNUM(big)->as.heap.digits; FL_SET_RAW(big, BIGNUM_EMBED_FLAG); - BIGNUM_SET_LEN(big, len); + 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); + } + } } } @@ -3092,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; @@ -3116,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; } @@ -3169,7 +3172,7 @@ static VALUE bignorm(VALUE x) { if (RB_BIGNUM_TYPE_P(x)) { - x = bigfixize(x); + x = bigfixize(x); } return x; } @@ -3191,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 @@ -3211,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; } @@ -3377,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; } @@ -3425,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 } @@ -3696,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 { @@ -3708,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); } @@ -3762,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; @@ -3972,7 +3975,7 @@ str2big_karatsuba( return z; } -#ifdef USE_GMP +#if USE_GMP static VALUE str2big_gmp( int sign, @@ -4039,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; } @@ -4064,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; @@ -4081,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); @@ -4167,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); @@ -4258,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; } 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 @@ -4313,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, @@ -4352,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); @@ -4394,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); @@ -4415,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) { @@ -4437,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); @@ -4471,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 @@ -4491,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; } @@ -4506,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); } @@ -4528,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); @@ -4546,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; } @@ -4582,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); @@ -4716,7 +4723,7 @@ 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]; @@ -4767,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; @@ -4784,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; @@ -4814,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; } @@ -4852,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; @@ -4956,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'"); @@ -4997,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; @@ -5006,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); } @@ -5025,7 +5032,7 @@ 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) { @@ -5072,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); @@ -5081,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'"); @@ -5096,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); } @@ -5132,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 @@ -5185,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 @@ -5241,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; @@ -5277,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; @@ -5306,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; @@ -5330,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; } @@ -5404,7 +5411,7 @@ 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) @@ -5412,18 +5419,14 @@ 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_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); @@ -5435,26 +5438,26 @@ 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); } @@ -5473,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; } @@ -5531,7 +5534,7 @@ rb_big_le(VALUE x, VALUE y) * * Returns <code>true</code> only if <i>obj</i> has the same value * as <i>big</i>. Contrast this with Integer#eql?, which requires - * <i>obj</i> to be a Integer. + * <i>obj</i> to be an Integer. * * 68719476736 == 68719476736.0 #=> true */ @@ -5540,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)) { } @@ -5548,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 @@ -5562,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 @@ -5657,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); @@ -5672,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; @@ -5690,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++) { @@ -5706,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++) { @@ -5720,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); @@ -5763,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; @@ -5782,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; @@ -5817,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); @@ -5842,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, '+'); } } @@ -5871,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, '-'); } } @@ -5949,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)); @@ -5984,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); @@ -6063,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; } } @@ -6078,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); @@ -6121,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); @@ -6137,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); @@ -6153,7 +6154,7 @@ 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, idDivmod); @@ -6167,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; } @@ -6193,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); @@ -6229,19 +6230,19 @@ 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, idFdiv)); @@ -6266,20 +6267,20 @@ 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, LONG2NUM(-yy)); @@ -6288,31 +6289,31 @@ rb_big_pow(VALUE x, VALUE y) 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)); } @@ -6332,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 @@ -6362,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); @@ -6385,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) { @@ -6412,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); @@ -6504,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) { @@ -6531,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); @@ -6598,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) { @@ -6622,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); @@ -6641,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); } } @@ -6671,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; @@ -6680,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); } } @@ -6703,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)) @@ -6749,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] */ @@ -6778,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; } @@ -6846,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; } @@ -6887,21 +6886,21 @@ 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; @@ -6933,37 +6932,35 @@ 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) { @@ -6989,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; @@ -7155,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); } @@ -7164,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); } } @@ -7191,13 +7190,9 @@ rb_int_powm(int const argc, VALUE * const argv, VALUE const num) void Init_Bignum(void) { - /* 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 2435224fe1..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, trim_mode: trim) - else - # [deprecated] This will be removed at Ruby 2.7. - erb = factory.new(src, 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 - 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/racc b/bin/racc deleted file mode 100755 index 3ddac532b4..0000000000 --- a/bin/racc +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env ruby -# -# This file was generated by RubyGems. -# -# The application 'racc' is installed as part of a gem, and -# this file is here to facilitate running it. -# - -require 'rubygems' - -version = ">= 0.a" - -if ARGV.first - str = ARGV.first - str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding - if str =~ /\A_(.*)_\z/ and Gem::Version.correct?($1) then - version = $1 - ARGV.shift - end -end - -if Gem.respond_to?(:activate_bin_path) -load Gem.activate_bin_path('racc', 'racc', version) -else -gem "racc", version -load Gem.bin_path("racc", "racc", 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/runner.rb b/bootstraptest/runner.rb index 56b4b12230..f9b3e919b8 100755 --- a/bootstraptest/runner.rb +++ b/bootstraptest/runner.rb @@ -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,22 +218,23 @@ 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 @@ -145,14 +243,16 @@ End 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,265 +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 @tty and !@verbose + 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 - @errbuf.each do |msg| + + # 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 - if @error == 0 - if @count == 0 - $stderr.puts "No tests, no problem" + + 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 - $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 - if @tty and !@verbose - $stderr.printf("%-*s%s", @width, @basename, @progress[@count % @progress.size]) + 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 end -rescue Interrupt - $stderr.puts "\##{@count} #{@location}" - raise -rescue Exception => err - $stderr.print 'E' - $stderr.puts if @verbose - error err.message, message -end -def show_limit(testsrc, opt = '', **argh) - result = get_result_string(testsrc, opt, **argh) - if @tty and @verbose - $stderr.puts ".{#@reset}\n#{erase}#{result}" - else - @errbuf.push result + 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 + + 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 = '') - if RubyVM.const_defined? :MJIT - timeout_seconds *= 3 if RubyVM::MJIT.enabled? # for --jit-wait - end - 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) @@ -436,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 @@ -513,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 5d2593c306..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 @@ -191,7 +218,7 @@ assert_equal %q{[10, main]}, %q{ %w[break next redo].each do |keyword| assert_match %r"Can't escape from eval with #{keyword}\b", %{ - STDERR.reopen(STDOUT) + $stderr = STDOUT begin eval "0 rescue #{keyword}" rescue SyntaxError => e @@ -201,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") diff --git a/bootstraptest/test_fiber.rb b/bootstraptest/test_fiber.rb index 35e1bf6851..2614dd13bf 100644 --- a/bootstraptest/test_fiber.rb +++ b/bootstraptest/test_fiber.rb @@ -19,12 +19,12 @@ assert_equal %q{ok}, %q{ } assert_equal %q{ok}, %q{ - 10_000.times.collect{Fiber.new{}} + 100.times.collect{Fiber.new{}} :ok } assert_equal %q{ok}, %q{ - fibers = 100.times.collect{Fiber.new{Fiber.yield}} + fibers = 1000.times.collect{Fiber.new{Fiber.yield}} fibers.each(&:resume) fibers.each(&:resume) :ok diff --git a/bootstraptest/test_insns.rb b/bootstraptest/test_insns.rb index 8addbf7b83..91fba9b011 100644 --- a/bootstraptest/test_insns.rb +++ b/bootstraptest/test_insns.rb @@ -86,11 +86,8 @@ 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}" }, ], @@ -123,6 +120,7 @@ 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', %q{ !!defined?([[]]) }, ], [ 'swap', <<-'},', ], # { x = [[false, true]] for i, j in x # here @@ -387,14 +385,13 @@ 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', <<-'},', ], # { 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 18a2737ea3..d07c47a56d 100644 --- a/bootstraptest/test_jump.rb +++ b/bootstraptest/test_jump.rb @@ -147,7 +147,7 @@ assert_equal %q{131}, %q{ } } assert_match %r{Invalid retry}, %q{ -STDERR.reopen(STDOUT) +$stderr = STDOUT begin eval %q{ 1.times{ @@ -297,7 +297,7 @@ assert_equal "true", %q{ }, '[ruby-core:21379]' assert_match %r{Invalid yield}, %q{ -STDERR.reopen(STDOUT) +$stderr = STDOUT begin eval %q{ class Object 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_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_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 fa27bf2aeb..948e2d7809 100644 --- a/bootstraptest/test_syntax.rb +++ b/bootstraptest/test_syntax.rb @@ -628,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 38a55ff229..5361828403 100644 --- a/bootstraptest/test_thread.rb +++ b/bootstraptest/test_thread.rb @@ -243,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 @@ -254,7 +254,7 @@ assert_equal 'ok', %{ end END end - require "./zzz.rb" + require "./zzz_t1.rb" $result } @@ -408,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 @@ -432,7 +432,7 @@ assert_equal 'ok', %{ end end; end - require "./zzz.rb" + require "./zzz_t2.rb" $result } 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 @@ -0,0 +1,241023 @@ +# This is a torture test for the JIT. +# There are 30K tiny methods with if-else statements in a 30-deep call hierarchy. +assert_equal '100000', %q{ + +def fun_l0_n0(x) + if (x < 1) + fun_l1_n310(x) + else + fun_l1_n485(x) + end +end + +def fun_l0_n1(x) + if (x < 1) + fun_l1_n930(x) + else + fun_l1_n418(x) + end +end + +def fun_l0_n2(x) + if (x < 1) + fun_l1_n549(x) + else + fun_l1_n44(x) + end +end + +def fun_l0_n3(x) + if (x < 1) + fun_l1_n394(x) + else + fun_l1_n447(x) + end +end + +def fun_l0_n4(x) + if (x < 1) + fun_l1_n683(x) + else + fun_l1_n547(x) + end +end + +def fun_l0_n5(x) + if (x < 1) + fun_l1_n320(x) + else + fun_l1_n896(x) + end +end + +def fun_l0_n6(x) + if (x < 1) + fun_l1_n649(x) + else + fun_l1_n243(x) + end +end + +def fun_l0_n7(x) + if (x < 1) + fun_l1_n100(x) + else + fun_l1_n243(x) + end +end + +def fun_l0_n8(x) + if (x < 1) + fun_l1_n839(x) + else + fun_l1_n720(x) + end +end + +def fun_l0_n9(x) + if (x < 1) + fun_l1_n177(x) + else + fun_l1_n555(x) + end +end + +def fun_l0_n10(x) + if (x < 1) + fun_l1_n814(x) + else + fun_l1_n900(x) + end +end + +def fun_l0_n11(x) + if (x < 1) + fun_l1_n585(x) + else + fun_l1_n901(x) + end +end + +def fun_l0_n12(x) + if (x < 1) + fun_l1_n952(x) + else + fun_l1_n270(x) + end +end + +def fun_l0_n13(x) + if (x < 1) + fun_l1_n172(x) + else + fun_l1_n209(x) + end +end + +def fun_l0_n14(x) + if (x < 1) + fun_l1_n514(x) + else + fun_l1_n414(x) + end +end + +def fun_l0_n15(x) + if (x < 1) + fun_l1_n190(x) + else + fun_l1_n100(x) + end +end + +def fun_l0_n16(x) + if (x < 1) + fun_l1_n696(x) + else + fun_l1_n997(x) + end +end + +def fun_l0_n17(x) + if (x < 1) + fun_l1_n568(x) + else + fun_l1_n820(x) + end +end + +def fun_l0_n18(x) + if (x < 1) + fun_l1_n837(x) + else + fun_l1_n588(x) + end +end + +def fun_l0_n19(x) + if (x < 1) + fun_l1_n206(x) + else + fun_l1_n126(x) + end +end + +def fun_l0_n20(x) + if (x < 1) + fun_l1_n317(x) + else + fun_l1_n722(x) + end +end + +def fun_l0_n21(x) + if (x < 1) + fun_l1_n614(x) + else + fun_l1_n372(x) + end +end + +def fun_l0_n22(x) + if (x < 1) + fun_l1_n530(x) + else + fun_l1_n862(x) + end +end + +def fun_l0_n23(x) + if (x < 1) + fun_l1_n889(x) + else + fun_l1_n271(x) + end +end + +def fun_l0_n24(x) + if (x < 1) + fun_l1_n996(x) + else + fun_l1_n717(x) + end +end + +def fun_l0_n25(x) + if (x < 1) + fun_l1_n726(x) + else + fun_l1_n939(x) + end +end + +def fun_l0_n26(x) + if (x < 1) + fun_l1_n316(x) + else + fun_l1_n293(x) + end +end + +def fun_l0_n27(x) + if (x < 1) + fun_l1_n90(x) + else + fun_l1_n596(x) + end +end + +def fun_l0_n28(x) + if (x < 1) + fun_l1_n743(x) + else + fun_l1_n782(x) + end +end + +def fun_l0_n29(x) + if (x < 1) + fun_l1_n896(x) + else + fun_l1_n247(x) + end +end + +def fun_l0_n30(x) + if (x < 1) + fun_l1_n2(x) + else + fun_l1_n377(x) + end +end + +def fun_l0_n31(x) + if (x < 1) + fun_l1_n380(x) + else + fun_l1_n655(x) + end +end + +def fun_l0_n32(x) + if (x < 1) + fun_l1_n572(x) + else + fun_l1_n778(x) + end +end + +def fun_l0_n33(x) + if (x < 1) + fun_l1_n485(x) + else + fun_l1_n282(x) + end +end + +def fun_l0_n34(x) + if (x < 1) + fun_l1_n703(x) + else + fun_l1_n179(x) + end +end + +def fun_l0_n35(x) + if (x < 1) + fun_l1_n281(x) + else + fun_l1_n572(x) + end +end + +def fun_l0_n36(x) + if (x < 1) + fun_l1_n48(x) + else + fun_l1_n286(x) + end +end + +def fun_l0_n37(x) + if (x < 1) + fun_l1_n539(x) + else + fun_l1_n86(x) + end +end + +def fun_l0_n38(x) + if (x < 1) + fun_l1_n918(x) + else + fun_l1_n778(x) + end +end + +def fun_l0_n39(x) + if (x < 1) + fun_l1_n832(x) + else + fun_l1_n94(x) + end +end + +def fun_l0_n40(x) + if (x < 1) + fun_l1_n213(x) + else + fun_l1_n580(x) + end +end + +def fun_l0_n41(x) + if (x < 1) + fun_l1_n413(x) + else + fun_l1_n793(x) + end +end + +def fun_l0_n42(x) + if (x < 1) + fun_l1_n451(x) + else + fun_l1_n779(x) + end +end + +def fun_l0_n43(x) + if (x < 1) + fun_l1_n118(x) + else + fun_l1_n778(x) + end +end + +def fun_l0_n44(x) + if (x < 1) + fun_l1_n162(x) + else + fun_l1_n901(x) + end +end + +def fun_l0_n45(x) + if (x < 1) + fun_l1_n157(x) + else + fun_l1_n280(x) + end +end + +def fun_l0_n46(x) + if (x < 1) + fun_l1_n748(x) + else + fun_l1_n881(x) + end +end + +def fun_l0_n47(x) + if (x < 1) + fun_l1_n529(x) + else + fun_l1_n732(x) + end +end + +def fun_l0_n48(x) + if (x < 1) + fun_l1_n417(x) + else + fun_l1_n483(x) + end +end + +def fun_l0_n49(x) + if (x < 1) + fun_l1_n743(x) + else + fun_l1_n525(x) + end +end + +def fun_l0_n50(x) + if (x < 1) + fun_l1_n14(x) + else + fun_l1_n309(x) + end +end + +def fun_l0_n51(x) + if (x < 1) + fun_l1_n436(x) + else + fun_l1_n304(x) + end +end + +def fun_l0_n52(x) + if (x < 1) + fun_l1_n623(x) + else + fun_l1_n590(x) + end +end + +def fun_l0_n53(x) + if (x < 1) + fun_l1_n696(x) + else + fun_l1_n53(x) + end +end + +def fun_l0_n54(x) + if (x < 1) + fun_l1_n807(x) + else + fun_l1_n523(x) + end +end + +def fun_l0_n55(x) + if (x < 1) + fun_l1_n607(x) + else + fun_l1_n609(x) + end +end + +def fun_l0_n56(x) + if (x < 1) + fun_l1_n721(x) + else + fun_l1_n994(x) + end +end + +def fun_l0_n57(x) + if (x < 1) + fun_l1_n472(x) + else + fun_l1_n818(x) + end +end + +def fun_l0_n58(x) + if (x < 1) + fun_l1_n30(x) + else + fun_l1_n954(x) + end +end + +def fun_l0_n59(x) + if (x < 1) + fun_l1_n223(x) + else + fun_l1_n148(x) + end +end + +def fun_l0_n60(x) + if (x < 1) + fun_l1_n761(x) + else + fun_l1_n40(x) + end +end + +def fun_l0_n61(x) + if (x < 1) + fun_l1_n57(x) + else + fun_l1_n858(x) + end +end + +def fun_l0_n62(x) + if (x < 1) + fun_l1_n114(x) + else + fun_l1_n767(x) + end +end + +def fun_l0_n63(x) + if (x < 1) + fun_l1_n287(x) + else + fun_l1_n752(x) + end +end + +def fun_l0_n64(x) + if (x < 1) + fun_l1_n16(x) + else + fun_l1_n229(x) + end +end + +def fun_l0_n65(x) + if (x < 1) + fun_l1_n230(x) + else + fun_l1_n954(x) + end +end + +def fun_l0_n66(x) + if (x < 1) + fun_l1_n98(x) + else + fun_l1_n320(x) + end +end + +def fun_l0_n67(x) + if (x < 1) + fun_l1_n878(x) + else + fun_l1_n985(x) + end +end + +def fun_l0_n68(x) + if (x < 1) + fun_l1_n32(x) + else + fun_l1_n648(x) + end +end + +def fun_l0_n69(x) + if (x < 1) + fun_l1_n453(x) + else + fun_l1_n466(x) + end +end + +def fun_l0_n70(x) + if (x < 1) + fun_l1_n787(x) + else + fun_l1_n802(x) + end +end + +def fun_l0_n71(x) + if (x < 1) + fun_l1_n656(x) + else + fun_l1_n347(x) + end +end + +def fun_l0_n72(x) + if (x < 1) + fun_l1_n358(x) + else + fun_l1_n336(x) + end +end + +def fun_l0_n73(x) + if (x < 1) + fun_l1_n291(x) + else + fun_l1_n536(x) + end +end + +def fun_l0_n74(x) + if (x < 1) + fun_l1_n795(x) + else + fun_l1_n606(x) + end +end + +def fun_l0_n75(x) + if (x < 1) + fun_l1_n21(x) + else + fun_l1_n720(x) + end +end + +def fun_l0_n76(x) + if (x < 1) + fun_l1_n513(x) + else + fun_l1_n300(x) + end +end + +def fun_l0_n77(x) + if (x < 1) + fun_l1_n358(x) + else + fun_l1_n332(x) + end +end + +def fun_l0_n78(x) + if (x < 1) + fun_l1_n712(x) + else + fun_l1_n906(x) + end +end + +def fun_l0_n79(x) + if (x < 1) + fun_l1_n555(x) + else + fun_l1_n850(x) + end +end + +def fun_l0_n80(x) + if (x < 1) + fun_l1_n320(x) + else + fun_l1_n892(x) + end +end + +def fun_l0_n81(x) + if (x < 1) + fun_l1_n191(x) + else + fun_l1_n187(x) + end +end + +def fun_l0_n82(x) + if (x < 1) + fun_l1_n457(x) + else + fun_l1_n718(x) + end +end + +def fun_l0_n83(x) + if (x < 1) + fun_l1_n314(x) + else + fun_l1_n697(x) + end +end + +def fun_l0_n84(x) + if (x < 1) + fun_l1_n459(x) + else + fun_l1_n500(x) + end +end + +def fun_l0_n85(x) + if (x < 1) + fun_l1_n912(x) + else + fun_l1_n992(x) + end +end + +def fun_l0_n86(x) + if (x < 1) + fun_l1_n523(x) + else + fun_l1_n201(x) + end +end + +def fun_l0_n87(x) + if (x < 1) + fun_l1_n30(x) + else + fun_l1_n829(x) + end +end + +def fun_l0_n88(x) + if (x < 1) + fun_l1_n223(x) + else + fun_l1_n799(x) + end +end + +def fun_l0_n89(x) + if (x < 1) + fun_l1_n289(x) + else + fun_l1_n289(x) + end +end + +def fun_l0_n90(x) + if (x < 1) + fun_l1_n961(x) + else + fun_l1_n694(x) + end +end + +def fun_l0_n91(x) + if (x < 1) + fun_l1_n423(x) + else + fun_l1_n848(x) + end +end + +def fun_l0_n92(x) + if (x < 1) + fun_l1_n612(x) + else + fun_l1_n358(x) + end +end + +def fun_l0_n93(x) + if (x < 1) + fun_l1_n148(x) + else + fun_l1_n312(x) + end +end + +def fun_l0_n94(x) + if (x < 1) + fun_l1_n771(x) + else + fun_l1_n205(x) + end +end + +def fun_l0_n95(x) + if (x < 1) + fun_l1_n573(x) + else + fun_l1_n692(x) + end +end + +def fun_l0_n96(x) + if (x < 1) + fun_l1_n66(x) + else + fun_l1_n936(x) + end +end + +def fun_l0_n97(x) + if (x < 1) + fun_l1_n429(x) + else + fun_l1_n949(x) + end +end + +def fun_l0_n98(x) + if (x < 1) + fun_l1_n737(x) + else + fun_l1_n338(x) + end +end + +def fun_l0_n99(x) + if (x < 1) + fun_l1_n335(x) + else + fun_l1_n739(x) + end +end + +def fun_l0_n100(x) + if (x < 1) + fun_l1_n989(x) + else + fun_l1_n735(x) + end +end + +def fun_l0_n101(x) + if (x < 1) + fun_l1_n559(x) + else + fun_l1_n478(x) + end +end + +def fun_l0_n102(x) + if (x < 1) + fun_l1_n261(x) + else + fun_l1_n162(x) + end +end + +def fun_l0_n103(x) + if (x < 1) + fun_l1_n400(x) + else + fun_l1_n156(x) + end +end + +def fun_l0_n104(x) + if (x < 1) + fun_l1_n747(x) + else + fun_l1_n361(x) + end +end + +def fun_l0_n105(x) + if (x < 1) + fun_l1_n717(x) + else + fun_l1_n522(x) + end +end + +def fun_l0_n106(x) + if (x < 1) + fun_l1_n513(x) + else + fun_l1_n150(x) + end +end + +def fun_l0_n107(x) + if (x < 1) + fun_l1_n710(x) + else + fun_l1_n602(x) + end +end + +def fun_l0_n108(x) + if (x < 1) + fun_l1_n866(x) + else + fun_l1_n111(x) + end +end + +def fun_l0_n109(x) + if (x < 1) + fun_l1_n725(x) + else + fun_l1_n448(x) + end +end + +def fun_l0_n110(x) + if (x < 1) + fun_l1_n703(x) + else + fun_l1_n127(x) + end +end + +def fun_l0_n111(x) + if (x < 1) + fun_l1_n420(x) + else + fun_l1_n666(x) + end +end + +def fun_l0_n112(x) + if (x < 1) + fun_l1_n647(x) + else + fun_l1_n567(x) + end +end + +def fun_l0_n113(x) + if (x < 1) + fun_l1_n543(x) + else + fun_l1_n992(x) + end +end + +def fun_l0_n114(x) + if (x < 1) + fun_l1_n12(x) + else + fun_l1_n470(x) + end +end + +def fun_l0_n115(x) + if (x < 1) + fun_l1_n25(x) + else + fun_l1_n917(x) + end +end + +def fun_l0_n116(x) + if (x < 1) + fun_l1_n201(x) + else + fun_l1_n110(x) + end +end + +def fun_l0_n117(x) + if (x < 1) + fun_l1_n222(x) + else + fun_l1_n747(x) + end +end + +def fun_l0_n118(x) + if (x < 1) + fun_l1_n990(x) + else + fun_l1_n794(x) + end +end + +def fun_l0_n119(x) + if (x < 1) + fun_l1_n712(x) + else + fun_l1_n5(x) + end +end + +def fun_l0_n120(x) + if (x < 1) + fun_l1_n28(x) + else + fun_l1_n89(x) + end +end + +def fun_l0_n121(x) + if (x < 1) + fun_l1_n341(x) + else + fun_l1_n983(x) + end +end + +def fun_l0_n122(x) + if (x < 1) + fun_l1_n123(x) + else + fun_l1_n452(x) + end +end + +def fun_l0_n123(x) + if (x < 1) + fun_l1_n514(x) + else + fun_l1_n949(x) + end +end + +def fun_l0_n124(x) + if (x < 1) + fun_l1_n280(x) + else + fun_l1_n281(x) + end +end + +def fun_l0_n125(x) + if (x < 1) + fun_l1_n512(x) + else + fun_l1_n772(x) + end +end + +def fun_l0_n126(x) + if (x < 1) + fun_l1_n911(x) + else + fun_l1_n693(x) + end +end + +def fun_l0_n127(x) + if (x < 1) + fun_l1_n633(x) + else + fun_l1_n574(x) + end +end + +def fun_l0_n128(x) + if (x < 1) + fun_l1_n318(x) + else + fun_l1_n266(x) + end +end + +def fun_l0_n129(x) + if (x < 1) + fun_l1_n677(x) + else + fun_l1_n10(x) + end +end + +def fun_l0_n130(x) + if (x < 1) + fun_l1_n994(x) + else + fun_l1_n48(x) + end +end + +def fun_l0_n131(x) + if (x < 1) + fun_l1_n335(x) + else + fun_l1_n473(x) + end +end + +def fun_l0_n132(x) + if (x < 1) + fun_l1_n641(x) + else + fun_l1_n48(x) + end +end + +def fun_l0_n133(x) + if (x < 1) + fun_l1_n914(x) + else + fun_l1_n818(x) + end +end + +def fun_l0_n134(x) + if (x < 1) + fun_l1_n479(x) + else + fun_l1_n761(x) + end +end + +def fun_l0_n135(x) + if (x < 1) + fun_l1_n186(x) + else + fun_l1_n692(x) + end +end + +def fun_l0_n136(x) + if (x < 1) + fun_l1_n325(x) + else + fun_l1_n279(x) + end +end + +def fun_l0_n137(x) + if (x < 1) + fun_l1_n493(x) + else + fun_l1_n293(x) + end +end + +def fun_l0_n138(x) + if (x < 1) + fun_l1_n788(x) + else + fun_l1_n364(x) + end +end + +def fun_l0_n139(x) + if (x < 1) + fun_l1_n565(x) + else + fun_l1_n63(x) + end +end + +def fun_l0_n140(x) + if (x < 1) + fun_l1_n128(x) + else + fun_l1_n299(x) + end +end + +def fun_l0_n141(x) + if (x < 1) + fun_l1_n782(x) + else + fun_l1_n195(x) + end +end + +def fun_l0_n142(x) + if (x < 1) + fun_l1_n696(x) + else + fun_l1_n117(x) + end +end + +def fun_l0_n143(x) + if (x < 1) + fun_l1_n263(x) + else + fun_l1_n683(x) + end +end + +def fun_l0_n144(x) + if (x < 1) + fun_l1_n633(x) + else + fun_l1_n92(x) + end +end + +def fun_l0_n145(x) + if (x < 1) + fun_l1_n626(x) + else + fun_l1_n201(x) + end +end + +def fun_l0_n146(x) + if (x < 1) + fun_l1_n296(x) + else + fun_l1_n425(x) + end +end + +def fun_l0_n147(x) + if (x < 1) + fun_l1_n395(x) + else + fun_l1_n750(x) + end +end + +def fun_l0_n148(x) + if (x < 1) + fun_l1_n164(x) + else + fun_l1_n580(x) + end +end + +def fun_l0_n149(x) + if (x < 1) + fun_l1_n904(x) + else + fun_l1_n264(x) + end +end + +def fun_l0_n150(x) + if (x < 1) + fun_l1_n383(x) + else + fun_l1_n558(x) + end +end + +def fun_l0_n151(x) + if (x < 1) + fun_l1_n522(x) + else + fun_l1_n735(x) + end +end + +def fun_l0_n152(x) + if (x < 1) + fun_l1_n496(x) + else + fun_l1_n562(x) + end +end + +def fun_l0_n153(x) + if (x < 1) + fun_l1_n374(x) + else + fun_l1_n561(x) + end +end + +def fun_l0_n154(x) + if (x < 1) + fun_l1_n314(x) + else + fun_l1_n368(x) + end +end + +def fun_l0_n155(x) + if (x < 1) + fun_l1_n456(x) + else + fun_l1_n189(x) + end +end + +def fun_l0_n156(x) + if (x < 1) + fun_l1_n46(x) + else + fun_l1_n620(x) + end +end + +def fun_l0_n157(x) + if (x < 1) + fun_l1_n259(x) + else + fun_l1_n742(x) + end +end + +def fun_l0_n158(x) + if (x < 1) + fun_l1_n881(x) + else + fun_l1_n786(x) + end +end + +def fun_l0_n159(x) + if (x < 1) + fun_l1_n607(x) + else + fun_l1_n177(x) + end +end + +def fun_l0_n160(x) + if (x < 1) + fun_l1_n824(x) + else + fun_l1_n415(x) + end +end + +def fun_l0_n161(x) + if (x < 1) + fun_l1_n443(x) + else + fun_l1_n787(x) + end +end + +def fun_l0_n162(x) + if (x < 1) + fun_l1_n124(x) + else + fun_l1_n529(x) + end +end + +def fun_l0_n163(x) + if (x < 1) + fun_l1_n696(x) + else + fun_l1_n96(x) + end +end + +def fun_l0_n164(x) + if (x < 1) + fun_l1_n775(x) + else + fun_l1_n549(x) + end +end + +def fun_l0_n165(x) + if (x < 1) + fun_l1_n860(x) + else + fun_l1_n212(x) + end +end + +def fun_l0_n166(x) + if (x < 1) + fun_l1_n378(x) + else + fun_l1_n904(x) + end +end + +def fun_l0_n167(x) + if (x < 1) + fun_l1_n15(x) + else + fun_l1_n640(x) + end +end + +def fun_l0_n168(x) + if (x < 1) + fun_l1_n771(x) + else + fun_l1_n861(x) + end +end + +def fun_l0_n169(x) + if (x < 1) + fun_l1_n468(x) + else + fun_l1_n586(x) + end +end + +def fun_l0_n170(x) + if (x < 1) + fun_l1_n477(x) + else + fun_l1_n674(x) + end +end + +def fun_l0_n171(x) + if (x < 1) + fun_l1_n509(x) + else + fun_l1_n64(x) + end +end + +def fun_l0_n172(x) + if (x < 1) + fun_l1_n612(x) + else + fun_l1_n828(x) + end +end + +def fun_l0_n173(x) + if (x < 1) + fun_l1_n440(x) + else + fun_l1_n204(x) + end +end + +def fun_l0_n174(x) + if (x < 1) + fun_l1_n259(x) + else + fun_l1_n947(x) + end +end + +def fun_l0_n175(x) + if (x < 1) + fun_l1_n402(x) + else + fun_l1_n588(x) + end +end + +def fun_l0_n176(x) + if (x < 1) + fun_l1_n245(x) + else + fun_l1_n540(x) + end +end + +def fun_l0_n177(x) + if (x < 1) + fun_l1_n869(x) + else + fun_l1_n249(x) + end +end + +def fun_l0_n178(x) + if (x < 1) + fun_l1_n830(x) + else + fun_l1_n210(x) + end +end + +def fun_l0_n179(x) + if (x < 1) + fun_l1_n692(x) + else + fun_l1_n222(x) + end +end + +def fun_l0_n180(x) + if (x < 1) + fun_l1_n220(x) + else + fun_l1_n281(x) + end +end + +def fun_l0_n181(x) + if (x < 1) + fun_l1_n523(x) + else + fun_l1_n618(x) + end +end + +def fun_l0_n182(x) + if (x < 1) + fun_l1_n44(x) + else + fun_l1_n422(x) + end +end + +def fun_l0_n183(x) + if (x < 1) + fun_l1_n944(x) + else + fun_l1_n230(x) + end +end + +def fun_l0_n184(x) + if (x < 1) + fun_l1_n678(x) + else + fun_l1_n121(x) + end +end + +def fun_l0_n185(x) + if (x < 1) + fun_l1_n73(x) + else + fun_l1_n398(x) + end +end + +def fun_l0_n186(x) + if (x < 1) + fun_l1_n121(x) + else + fun_l1_n689(x) + end +end + +def fun_l0_n187(x) + if (x < 1) + fun_l1_n946(x) + else + fun_l1_n613(x) + end +end + +def fun_l0_n188(x) + if (x < 1) + fun_l1_n987(x) + else + fun_l1_n785(x) + end +end + +def fun_l0_n189(x) + if (x < 1) + fun_l1_n181(x) + else + fun_l1_n644(x) + end +end + +def fun_l0_n190(x) + if (x < 1) + fun_l1_n623(x) + else + fun_l1_n679(x) + end +end + +def fun_l0_n191(x) + if (x < 1) + fun_l1_n784(x) + else + fun_l1_n274(x) + end +end + +def fun_l0_n192(x) + if (x < 1) + fun_l1_n71(x) + else + fun_l1_n773(x) + end +end + +def fun_l0_n193(x) + if (x < 1) + fun_l1_n516(x) + else + fun_l1_n496(x) + end +end + +def fun_l0_n194(x) + if (x < 1) + fun_l1_n608(x) + else + fun_l1_n460(x) + end +end + +def fun_l0_n195(x) + if (x < 1) + fun_l1_n25(x) + else + fun_l1_n437(x) + end +end + +def fun_l0_n196(x) + if (x < 1) + fun_l1_n410(x) + else + fun_l1_n674(x) + end +end + +def fun_l0_n197(x) + if (x < 1) + fun_l1_n949(x) + else + fun_l1_n708(x) + end +end + +def fun_l0_n198(x) + if (x < 1) + fun_l1_n714(x) + else + fun_l1_n119(x) + end +end + +def fun_l0_n199(x) + if (x < 1) + fun_l1_n41(x) + else + fun_l1_n865(x) + end +end + +def fun_l0_n200(x) + if (x < 1) + fun_l1_n383(x) + else + fun_l1_n90(x) + end +end + +def fun_l0_n201(x) + if (x < 1) + fun_l1_n581(x) + else + fun_l1_n273(x) + end +end + +def fun_l0_n202(x) + if (x < 1) + fun_l1_n350(x) + else + fun_l1_n425(x) + end +end + +def fun_l0_n203(x) + if (x < 1) + fun_l1_n259(x) + else + fun_l1_n922(x) + end +end + +def fun_l0_n204(x) + if (x < 1) + fun_l1_n448(x) + else + fun_l1_n741(x) + end +end + +def fun_l0_n205(x) + if (x < 1) + fun_l1_n86(x) + else + fun_l1_n618(x) + end +end + +def fun_l0_n206(x) + if (x < 1) + fun_l1_n412(x) + else + fun_l1_n646(x) + end +end + +def fun_l0_n207(x) + if (x < 1) + fun_l1_n196(x) + else + fun_l1_n496(x) + end +end + +def fun_l0_n208(x) + if (x < 1) + fun_l1_n777(x) + else + fun_l1_n150(x) + end +end + +def fun_l0_n209(x) + if (x < 1) + fun_l1_n20(x) + else + fun_l1_n718(x) + end +end + +def fun_l0_n210(x) + if (x < 1) + fun_l1_n934(x) + else + fun_l1_n416(x) + end +end + +def fun_l0_n211(x) + if (x < 1) + fun_l1_n803(x) + else + fun_l1_n636(x) + end +end + +def fun_l0_n212(x) + if (x < 1) + fun_l1_n93(x) + else + fun_l1_n791(x) + end +end + +def fun_l0_n213(x) + if (x < 1) + fun_l1_n76(x) + else + fun_l1_n172(x) + end +end + +def fun_l0_n214(x) + if (x < 1) + fun_l1_n103(x) + else + fun_l1_n381(x) + end +end + +def fun_l0_n215(x) + if (x < 1) + fun_l1_n433(x) + else + fun_l1_n349(x) + end +end + +def fun_l0_n216(x) + if (x < 1) + fun_l1_n154(x) + else + fun_l1_n740(x) + end +end + +def fun_l0_n217(x) + if (x < 1) + fun_l1_n927(x) + else + fun_l1_n886(x) + end +end + +def fun_l0_n218(x) + if (x < 1) + fun_l1_n804(x) + else + fun_l1_n6(x) + end +end + +def fun_l0_n219(x) + if (x < 1) + fun_l1_n921(x) + else + fun_l1_n121(x) + end +end + +def fun_l0_n220(x) + if (x < 1) + fun_l1_n732(x) + else + fun_l1_n224(x) + end +end + +def fun_l0_n221(x) + if (x < 1) + fun_l1_n818(x) + else + fun_l1_n105(x) + end +end + +def fun_l0_n222(x) + if (x < 1) + fun_l1_n162(x) + else + fun_l1_n700(x) + end +end + +def fun_l0_n223(x) + if (x < 1) + fun_l1_n57(x) + else + fun_l1_n734(x) + end +end + +def fun_l0_n224(x) + if (x < 1) + fun_l1_n283(x) + else + fun_l1_n87(x) + end +end + +def fun_l0_n225(x) + if (x < 1) + fun_l1_n623(x) + else + fun_l1_n363(x) + end +end + +def fun_l0_n226(x) + if (x < 1) + fun_l1_n962(x) + else + fun_l1_n660(x) + end +end + +def fun_l0_n227(x) + if (x < 1) + fun_l1_n255(x) + else + fun_l1_n536(x) + end +end + +def fun_l0_n228(x) + if (x < 1) + fun_l1_n377(x) + else + fun_l1_n249(x) + end +end + +def fun_l0_n229(x) + if (x < 1) + fun_l1_n527(x) + else + fun_l1_n691(x) + end +end + +def fun_l0_n230(x) + if (x < 1) + fun_l1_n781(x) + else + fun_l1_n494(x) + end +end + +def fun_l0_n231(x) + if (x < 1) + fun_l1_n889(x) + else + fun_l1_n967(x) + end +end + +def fun_l0_n232(x) + if (x < 1) + fun_l1_n509(x) + else + fun_l1_n910(x) + end +end + +def fun_l0_n233(x) + if (x < 1) + fun_l1_n354(x) + else + fun_l1_n5(x) + end +end + +def fun_l0_n234(x) + if (x < 1) + fun_l1_n423(x) + else + fun_l1_n788(x) + end +end + +def fun_l0_n235(x) + if (x < 1) + fun_l1_n632(x) + else + fun_l1_n705(x) + end +end + +def fun_l0_n236(x) + if (x < 1) + fun_l1_n955(x) + else + fun_l1_n908(x) + end +end + +def fun_l0_n237(x) + if (x < 1) + fun_l1_n329(x) + else + fun_l1_n2(x) + end +end + +def fun_l0_n238(x) + if (x < 1) + fun_l1_n767(x) + else + fun_l1_n766(x) + end +end + +def fun_l0_n239(x) + if (x < 1) + fun_l1_n304(x) + else + fun_l1_n507(x) + end +end + +def fun_l0_n240(x) + if (x < 1) + fun_l1_n845(x) + else + fun_l1_n777(x) + end +end + +def fun_l0_n241(x) + if (x < 1) + fun_l1_n69(x) + else + fun_l1_n260(x) + end +end + +def fun_l0_n242(x) + if (x < 1) + fun_l1_n194(x) + else + fun_l1_n412(x) + end +end + +def fun_l0_n243(x) + if (x < 1) + fun_l1_n974(x) + else + fun_l1_n922(x) + end +end + +def fun_l0_n244(x) + if (x < 1) + fun_l1_n532(x) + else + fun_l1_n530(x) + end +end + +def fun_l0_n245(x) + if (x < 1) + fun_l1_n554(x) + else + fun_l1_n656(x) + end +end + +def fun_l0_n246(x) + if (x < 1) + fun_l1_n253(x) + else + fun_l1_n235(x) + end +end + +def fun_l0_n247(x) + if (x < 1) + fun_l1_n754(x) + else + fun_l1_n775(x) + end +end + +def fun_l0_n248(x) + if (x < 1) + fun_l1_n89(x) + else + fun_l1_n129(x) + end +end + +def fun_l0_n249(x) + if (x < 1) + fun_l1_n284(x) + else + fun_l1_n642(x) + end +end + +def fun_l0_n250(x) + if (x < 1) + fun_l1_n67(x) + else + fun_l1_n867(x) + end +end + +def fun_l0_n251(x) + if (x < 1) + fun_l1_n926(x) + else + fun_l1_n442(x) + end +end + +def fun_l0_n252(x) + if (x < 1) + fun_l1_n196(x) + else + fun_l1_n583(x) + end +end + +def fun_l0_n253(x) + if (x < 1) + fun_l1_n966(x) + else + fun_l1_n810(x) + end +end + +def fun_l0_n254(x) + if (x < 1) + fun_l1_n984(x) + else + fun_l1_n752(x) + end +end + +def fun_l0_n255(x) + if (x < 1) + fun_l1_n941(x) + else + fun_l1_n744(x) + end +end + +def fun_l0_n256(x) + if (x < 1) + fun_l1_n785(x) + else + fun_l1_n610(x) + end +end + +def fun_l0_n257(x) + if (x < 1) + fun_l1_n622(x) + else + fun_l1_n730(x) + end +end + +def fun_l0_n258(x) + if (x < 1) + fun_l1_n453(x) + else + fun_l1_n641(x) + end +end + +def fun_l0_n259(x) + if (x < 1) + fun_l1_n407(x) + else + fun_l1_n818(x) + end +end + +def fun_l0_n260(x) + if (x < 1) + fun_l1_n252(x) + else + fun_l1_n205(x) + end +end + +def fun_l0_n261(x) + if (x < 1) + fun_l1_n940(x) + else + fun_l1_n678(x) + end +end + +def fun_l0_n262(x) + if (x < 1) + fun_l1_n350(x) + else + fun_l1_n496(x) + end +end + +def fun_l0_n263(x) + if (x < 1) + fun_l1_n688(x) + else + fun_l1_n193(x) + end +end + +def fun_l0_n264(x) + if (x < 1) + fun_l1_n366(x) + else + fun_l1_n790(x) + end +end + +def fun_l0_n265(x) + if (x < 1) + fun_l1_n592(x) + else + fun_l1_n93(x) + end +end + +def fun_l0_n266(x) + if (x < 1) + fun_l1_n189(x) + else + fun_l1_n467(x) + end +end + +def fun_l0_n267(x) + if (x < 1) + fun_l1_n813(x) + else + fun_l1_n7(x) + end +end + +def fun_l0_n268(x) + if (x < 1) + fun_l1_n516(x) + else + fun_l1_n271(x) + end +end + +def fun_l0_n269(x) + if (x < 1) + fun_l1_n0(x) + else + fun_l1_n121(x) + end +end + +def fun_l0_n270(x) + if (x < 1) + fun_l1_n634(x) + else + fun_l1_n726(x) + end +end + +def fun_l0_n271(x) + if (x < 1) + fun_l1_n192(x) + else + fun_l1_n468(x) + end +end + +def fun_l0_n272(x) + if (x < 1) + fun_l1_n278(x) + else + fun_l1_n355(x) + end +end + +def fun_l0_n273(x) + if (x < 1) + fun_l1_n354(x) + else + fun_l1_n87(x) + end +end + +def fun_l0_n274(x) + if (x < 1) + fun_l1_n643(x) + else + fun_l1_n806(x) + end +end + +def fun_l0_n275(x) + if (x < 1) + fun_l1_n389(x) + else + fun_l1_n559(x) + end +end + +def fun_l0_n276(x) + if (x < 1) + fun_l1_n283(x) + else + fun_l1_n539(x) + end +end + +def fun_l0_n277(x) + if (x < 1) + fun_l1_n0(x) + else + fun_l1_n351(x) + end +end + +def fun_l0_n278(x) + if (x < 1) + fun_l1_n813(x) + else + fun_l1_n513(x) + end +end + +def fun_l0_n279(x) + if (x < 1) + fun_l1_n501(x) + else + fun_l1_n967(x) + end +end + +def fun_l0_n280(x) + if (x < 1) + fun_l1_n727(x) + else + fun_l1_n232(x) + end +end + +def fun_l0_n281(x) + if (x < 1) + fun_l1_n946(x) + else + fun_l1_n693(x) + end +end + +def fun_l0_n282(x) + if (x < 1) + fun_l1_n260(x) + else + fun_l1_n525(x) + end +end + +def fun_l0_n283(x) + if (x < 1) + fun_l1_n957(x) + else + fun_l1_n817(x) + end +end + +def fun_l0_n284(x) + if (x < 1) + fun_l1_n91(x) + else + fun_l1_n735(x) + end +end + +def fun_l0_n285(x) + if (x < 1) + fun_l1_n71(x) + else + fun_l1_n24(x) + end +end + +def fun_l0_n286(x) + if (x < 1) + fun_l1_n594(x) + else + fun_l1_n747(x) + end +end + +def fun_l0_n287(x) + if (x < 1) + fun_l1_n869(x) + else + fun_l1_n414(x) + end +end + +def fun_l0_n288(x) + if (x < 1) + fun_l1_n182(x) + else + fun_l1_n835(x) + end +end + +def fun_l0_n289(x) + if (x < 1) + fun_l1_n188(x) + else + fun_l1_n949(x) + end +end + +def fun_l0_n290(x) + if (x < 1) + fun_l1_n35(x) + else + fun_l1_n259(x) + end +end + +def fun_l0_n291(x) + if (x < 1) + fun_l1_n593(x) + else + fun_l1_n810(x) + end +end + +def fun_l0_n292(x) + if (x < 1) + fun_l1_n680(x) + else + fun_l1_n564(x) + end +end + +def fun_l0_n293(x) + if (x < 1) + fun_l1_n57(x) + else + fun_l1_n91(x) + end +end + +def fun_l0_n294(x) + if (x < 1) + fun_l1_n945(x) + else + fun_l1_n807(x) + end +end + +def fun_l0_n295(x) + if (x < 1) + fun_l1_n442(x) + else + fun_l1_n123(x) + end +end + +def fun_l0_n296(x) + if (x < 1) + fun_l1_n536(x) + else + fun_l1_n987(x) + end +end + +def fun_l0_n297(x) + if (x < 1) + fun_l1_n519(x) + else + fun_l1_n657(x) + end +end + +def fun_l0_n298(x) + if (x < 1) + fun_l1_n204(x) + else + fun_l1_n437(x) + end +end + +def fun_l0_n299(x) + if (x < 1) + fun_l1_n289(x) + else + fun_l1_n385(x) + end +end + +def fun_l0_n300(x) + if (x < 1) + fun_l1_n629(x) + else + fun_l1_n803(x) + end +end + +def fun_l0_n301(x) + if (x < 1) + fun_l1_n576(x) + else + fun_l1_n921(x) + end +end + +def fun_l0_n302(x) + if (x < 1) + fun_l1_n466(x) + else + fun_l1_n475(x) + end +end + +def fun_l0_n303(x) + if (x < 1) + fun_l1_n658(x) + else + fun_l1_n333(x) + end +end + +def fun_l0_n304(x) + if (x < 1) + fun_l1_n728(x) + else + fun_l1_n749(x) + end +end + +def fun_l0_n305(x) + if (x < 1) + fun_l1_n68(x) + else + fun_l1_n552(x) + end +end + +def fun_l0_n306(x) + if (x < 1) + fun_l1_n633(x) + else + fun_l1_n958(x) + end +end + +def fun_l0_n307(x) + if (x < 1) + fun_l1_n79(x) + else + fun_l1_n386(x) + end +end + +def fun_l0_n308(x) + if (x < 1) + fun_l1_n831(x) + else + fun_l1_n176(x) + end +end + +def fun_l0_n309(x) + if (x < 1) + fun_l1_n770(x) + else + fun_l1_n334(x) + end +end + +def fun_l0_n310(x) + if (x < 1) + fun_l1_n21(x) + else + fun_l1_n643(x) + end +end + +def fun_l0_n311(x) + if (x < 1) + fun_l1_n673(x) + else + fun_l1_n298(x) + end +end + +def fun_l0_n312(x) + if (x < 1) + fun_l1_n753(x) + else + fun_l1_n817(x) + end +end + +def fun_l0_n313(x) + if (x < 1) + fun_l1_n299(x) + else + fun_l1_n350(x) + end +end + +def fun_l0_n314(x) + if (x < 1) + fun_l1_n190(x) + else + fun_l1_n519(x) + end +end + +def fun_l0_n315(x) + if (x < 1) + fun_l1_n934(x) + else + fun_l1_n416(x) + end +end + +def fun_l0_n316(x) + if (x < 1) + fun_l1_n695(x) + else + fun_l1_n377(x) + end +end + +def fun_l0_n317(x) + if (x < 1) + fun_l1_n194(x) + else + fun_l1_n747(x) + end +end + +def fun_l0_n318(x) + if (x < 1) + fun_l1_n145(x) + else + fun_l1_n761(x) + end +end + +def fun_l0_n319(x) + if (x < 1) + fun_l1_n223(x) + else + fun_l1_n237(x) + end +end + +def fun_l0_n320(x) + if (x < 1) + fun_l1_n907(x) + else + fun_l1_n653(x) + end +end + +def fun_l0_n321(x) + if (x < 1) + fun_l1_n216(x) + else + fun_l1_n67(x) + end +end + +def fun_l0_n322(x) + if (x < 1) + fun_l1_n658(x) + else + fun_l1_n948(x) + end +end + +def fun_l0_n323(x) + if (x < 1) + fun_l1_n358(x) + else + fun_l1_n926(x) + end +end + +def fun_l0_n324(x) + if (x < 1) + fun_l1_n533(x) + else + fun_l1_n252(x) + end +end + +def fun_l0_n325(x) + if (x < 1) + fun_l1_n284(x) + else + fun_l1_n670(x) + end +end + +def fun_l0_n326(x) + if (x < 1) + fun_l1_n327(x) + else + fun_l1_n298(x) + end +end + +def fun_l0_n327(x) + if (x < 1) + fun_l1_n837(x) + else + fun_l1_n890(x) + end +end + +def fun_l0_n328(x) + if (x < 1) + fun_l1_n941(x) + else + fun_l1_n794(x) + end +end + +def fun_l0_n329(x) + if (x < 1) + fun_l1_n859(x) + else + fun_l1_n970(x) + end +end + +def fun_l0_n330(x) + if (x < 1) + fun_l1_n282(x) + else + fun_l1_n569(x) + end +end + +def fun_l0_n331(x) + if (x < 1) + fun_l1_n33(x) + else + fun_l1_n525(x) + end +end + +def fun_l0_n332(x) + if (x < 1) + fun_l1_n847(x) + else + fun_l1_n540(x) + end +end + +def fun_l0_n333(x) + if (x < 1) + fun_l1_n485(x) + else + fun_l1_n756(x) + end +end + +def fun_l0_n334(x) + if (x < 1) + fun_l1_n396(x) + else + fun_l1_n781(x) + end +end + +def fun_l0_n335(x) + if (x < 1) + fun_l1_n225(x) + else + fun_l1_n417(x) + end +end + +def fun_l0_n336(x) + if (x < 1) + fun_l1_n906(x) + else + fun_l1_n301(x) + end +end + +def fun_l0_n337(x) + if (x < 1) + fun_l1_n863(x) + else + fun_l1_n409(x) + end +end + +def fun_l0_n338(x) + if (x < 1) + fun_l1_n103(x) + else + fun_l1_n460(x) + end +end + +def fun_l0_n339(x) + if (x < 1) + fun_l1_n787(x) + else + fun_l1_n434(x) + end +end + +def fun_l0_n340(x) + if (x < 1) + fun_l1_n308(x) + else + fun_l1_n875(x) + end +end + +def fun_l0_n341(x) + if (x < 1) + fun_l1_n146(x) + else + fun_l1_n566(x) + end +end + +def fun_l0_n342(x) + if (x < 1) + fun_l1_n837(x) + else + fun_l1_n770(x) + end +end + +def fun_l0_n343(x) + if (x < 1) + fun_l1_n668(x) + else + fun_l1_n60(x) + end +end + +def fun_l0_n344(x) + if (x < 1) + fun_l1_n305(x) + else + fun_l1_n388(x) + end +end + +def fun_l0_n345(x) + if (x < 1) + fun_l1_n796(x) + else + fun_l1_n920(x) + end +end + +def fun_l0_n346(x) + if (x < 1) + fun_l1_n257(x) + else + fun_l1_n229(x) + end +end + +def fun_l0_n347(x) + if (x < 1) + fun_l1_n740(x) + else + fun_l1_n372(x) + end +end + +def fun_l0_n348(x) + if (x < 1) + fun_l1_n698(x) + else + fun_l1_n63(x) + end +end + +def fun_l0_n349(x) + if (x < 1) + fun_l1_n4(x) + else + fun_l1_n119(x) + end +end + +def fun_l0_n350(x) + if (x < 1) + fun_l1_n186(x) + else + fun_l1_n352(x) + end +end + +def fun_l0_n351(x) + if (x < 1) + fun_l1_n619(x) + else + fun_l1_n583(x) + end +end + +def fun_l0_n352(x) + if (x < 1) + fun_l1_n2(x) + else + fun_l1_n275(x) + end +end + +def fun_l0_n353(x) + if (x < 1) + fun_l1_n843(x) + else + fun_l1_n986(x) + end +end + +def fun_l0_n354(x) + if (x < 1) + fun_l1_n865(x) + else + fun_l1_n64(x) + end +end + +def fun_l0_n355(x) + if (x < 1) + fun_l1_n732(x) + else + fun_l1_n332(x) + end +end + +def fun_l0_n356(x) + if (x < 1) + fun_l1_n642(x) + else + fun_l1_n559(x) + end +end + +def fun_l0_n357(x) + if (x < 1) + fun_l1_n385(x) + else + fun_l1_n532(x) + end +end + +def fun_l0_n358(x) + if (x < 1) + fun_l1_n672(x) + else + fun_l1_n721(x) + end +end + +def fun_l0_n359(x) + if (x < 1) + fun_l1_n663(x) + else + fun_l1_n590(x) + end +end + +def fun_l0_n360(x) + if (x < 1) + fun_l1_n240(x) + else + fun_l1_n743(x) + end +end + +def fun_l0_n361(x) + if (x < 1) + fun_l1_n301(x) + else + fun_l1_n943(x) + end +end + +def fun_l0_n362(x) + if (x < 1) + fun_l1_n108(x) + else + fun_l1_n975(x) + end +end + +def fun_l0_n363(x) + if (x < 1) + fun_l1_n554(x) + else + fun_l1_n135(x) + end +end + +def fun_l0_n364(x) + if (x < 1) + fun_l1_n360(x) + else + fun_l1_n821(x) + end +end + +def fun_l0_n365(x) + if (x < 1) + fun_l1_n992(x) + else + fun_l1_n158(x) + end +end + +def fun_l0_n366(x) + if (x < 1) + fun_l1_n439(x) + else + fun_l1_n506(x) + end +end + +def fun_l0_n367(x) + if (x < 1) + fun_l1_n229(x) + else + fun_l1_n714(x) + end +end + +def fun_l0_n368(x) + if (x < 1) + fun_l1_n459(x) + else + fun_l1_n874(x) + end +end + +def fun_l0_n369(x) + if (x < 1) + fun_l1_n799(x) + else + fun_l1_n406(x) + end +end + +def fun_l0_n370(x) + if (x < 1) + fun_l1_n791(x) + else + fun_l1_n669(x) + end +end + +def fun_l0_n371(x) + if (x < 1) + fun_l1_n99(x) + else + fun_l1_n344(x) + end +end + +def fun_l0_n372(x) + if (x < 1) + fun_l1_n585(x) + else + fun_l1_n388(x) + end +end + +def fun_l0_n373(x) + if (x < 1) + fun_l1_n35(x) + else + fun_l1_n369(x) + end +end + +def fun_l0_n374(x) + if (x < 1) + fun_l1_n41(x) + else + fun_l1_n378(x) + end +end + +def fun_l0_n375(x) + if (x < 1) + fun_l1_n372(x) + else + fun_l1_n406(x) + end +end + +def fun_l0_n376(x) + if (x < 1) + fun_l1_n247(x) + else + fun_l1_n563(x) + end +end + +def fun_l0_n377(x) + if (x < 1) + fun_l1_n998(x) + else + fun_l1_n302(x) + end +end + +def fun_l0_n378(x) + if (x < 1) + fun_l1_n955(x) + else + fun_l1_n333(x) + end +end + +def fun_l0_n379(x) + if (x < 1) + fun_l1_n554(x) + else + fun_l1_n863(x) + end +end + +def fun_l0_n380(x) + if (x < 1) + fun_l1_n248(x) + else + fun_l1_n297(x) + end +end + +def fun_l0_n381(x) + if (x < 1) + fun_l1_n85(x) + else + fun_l1_n726(x) + end +end + +def fun_l0_n382(x) + if (x < 1) + fun_l1_n75(x) + else + fun_l1_n990(x) + end +end + +def fun_l0_n383(x) + if (x < 1) + fun_l1_n353(x) + else + fun_l1_n218(x) + end +end + +def fun_l0_n384(x) + if (x < 1) + fun_l1_n103(x) + else + fun_l1_n192(x) + end +end + +def fun_l0_n385(x) + if (x < 1) + fun_l1_n659(x) + else + fun_l1_n996(x) + end +end + +def fun_l0_n386(x) + if (x < 1) + fun_l1_n528(x) + else + fun_l1_n654(x) + end +end + +def fun_l0_n387(x) + if (x < 1) + fun_l1_n857(x) + else + fun_l1_n638(x) + end +end + +def fun_l0_n388(x) + if (x < 1) + fun_l1_n311(x) + else + fun_l1_n877(x) + end +end + +def fun_l0_n389(x) + if (x < 1) + fun_l1_n406(x) + else + fun_l1_n191(x) + end +end + +def fun_l0_n390(x) + if (x < 1) + fun_l1_n464(x) + else + fun_l1_n684(x) + end +end + +def fun_l0_n391(x) + if (x < 1) + fun_l1_n878(x) + else + fun_l1_n635(x) + end +end + +def fun_l0_n392(x) + if (x < 1) + fun_l1_n863(x) + else + fun_l1_n486(x) + end +end + +def fun_l0_n393(x) + if (x < 1) + fun_l1_n587(x) + else + fun_l1_n387(x) + end +end + +def fun_l0_n394(x) + if (x < 1) + fun_l1_n553(x) + else + fun_l1_n789(x) + end +end + +def fun_l0_n395(x) + if (x < 1) + fun_l1_n420(x) + else + fun_l1_n148(x) + end +end + +def fun_l0_n396(x) + if (x < 1) + fun_l1_n802(x) + else + fun_l1_n845(x) + end +end + +def fun_l0_n397(x) + if (x < 1) + fun_l1_n749(x) + else + fun_l1_n742(x) + end +end + +def fun_l0_n398(x) + if (x < 1) + fun_l1_n929(x) + else + fun_l1_n458(x) + end +end + +def fun_l0_n399(x) + if (x < 1) + fun_l1_n908(x) + else + fun_l1_n188(x) + end +end + +def fun_l0_n400(x) + if (x < 1) + fun_l1_n932(x) + else + fun_l1_n854(x) + end +end + +def fun_l0_n401(x) + if (x < 1) + fun_l1_n29(x) + else + fun_l1_n666(x) + end +end + +def fun_l0_n402(x) + if (x < 1) + fun_l1_n877(x) + else + fun_l1_n518(x) + end +end + +def fun_l0_n403(x) + if (x < 1) + fun_l1_n639(x) + else + fun_l1_n618(x) + end +end + +def fun_l0_n404(x) + if (x < 1) + fun_l1_n177(x) + else + fun_l1_n487(x) + end +end + +def fun_l0_n405(x) + if (x < 1) + fun_l1_n536(x) + else + fun_l1_n860(x) + end +end + +def fun_l0_n406(x) + if (x < 1) + fun_l1_n626(x) + else + fun_l1_n894(x) + end +end + +def fun_l0_n407(x) + if (x < 1) + fun_l1_n533(x) + else + fun_l1_n367(x) + end +end + +def fun_l0_n408(x) + if (x < 1) + fun_l1_n146(x) + else + fun_l1_n546(x) + end +end + +def fun_l0_n409(x) + if (x < 1) + fun_l1_n872(x) + else + fun_l1_n387(x) + end +end + +def fun_l0_n410(x) + if (x < 1) + fun_l1_n726(x) + else + fun_l1_n973(x) + end +end + +def fun_l0_n411(x) + if (x < 1) + fun_l1_n168(x) + else + fun_l1_n783(x) + end +end + +def fun_l0_n412(x) + if (x < 1) + fun_l1_n895(x) + else + fun_l1_n901(x) + end +end + +def fun_l0_n413(x) + if (x < 1) + fun_l1_n235(x) + else + fun_l1_n593(x) + end +end + +def fun_l0_n414(x) + if (x < 1) + fun_l1_n328(x) + else + fun_l1_n693(x) + end +end + +def fun_l0_n415(x) + if (x < 1) + fun_l1_n882(x) + else + fun_l1_n290(x) + end +end + +def fun_l0_n416(x) + if (x < 1) + fun_l1_n433(x) + else + fun_l1_n220(x) + end +end + +def fun_l0_n417(x) + if (x < 1) + fun_l1_n966(x) + else + fun_l1_n74(x) + end +end + +def fun_l0_n418(x) + if (x < 1) + fun_l1_n750(x) + else + fun_l1_n547(x) + end +end + +def fun_l0_n419(x) + if (x < 1) + fun_l1_n94(x) + else + fun_l1_n794(x) + end +end + +def fun_l0_n420(x) + if (x < 1) + fun_l1_n68(x) + else + fun_l1_n970(x) + end +end + +def fun_l0_n421(x) + if (x < 1) + fun_l1_n663(x) + else + fun_l1_n388(x) + end +end + +def fun_l0_n422(x) + if (x < 1) + fun_l1_n686(x) + else + fun_l1_n67(x) + end +end + +def fun_l0_n423(x) + if (x < 1) + fun_l1_n210(x) + else + fun_l1_n64(x) + end +end + +def fun_l0_n424(x) + if (x < 1) + fun_l1_n375(x) + else + fun_l1_n205(x) + end +end + +def fun_l0_n425(x) + if (x < 1) + fun_l1_n832(x) + else + fun_l1_n325(x) + end +end + +def fun_l0_n426(x) + if (x < 1) + fun_l1_n819(x) + else + fun_l1_n601(x) + end +end + +def fun_l0_n427(x) + if (x < 1) + fun_l1_n588(x) + else + fun_l1_n875(x) + end +end + +def fun_l0_n428(x) + if (x < 1) + fun_l1_n682(x) + else + fun_l1_n523(x) + end +end + +def fun_l0_n429(x) + if (x < 1) + fun_l1_n972(x) + else + fun_l1_n891(x) + end +end + +def fun_l0_n430(x) + if (x < 1) + fun_l1_n90(x) + else + fun_l1_n655(x) + end +end + +def fun_l0_n431(x) + if (x < 1) + fun_l1_n393(x) + else + fun_l1_n454(x) + end +end + +def fun_l0_n432(x) + if (x < 1) + fun_l1_n435(x) + else + fun_l1_n860(x) + end +end + +def fun_l0_n433(x) + if (x < 1) + fun_l1_n516(x) + else + fun_l1_n246(x) + end +end + +def fun_l0_n434(x) + if (x < 1) + fun_l1_n216(x) + else + fun_l1_n78(x) + end +end + +def fun_l0_n435(x) + if (x < 1) + fun_l1_n357(x) + else + fun_l1_n61(x) + end +end + +def fun_l0_n436(x) + if (x < 1) + fun_l1_n289(x) + else + fun_l1_n507(x) + end +end + +def fun_l0_n437(x) + if (x < 1) + fun_l1_n182(x) + else + fun_l1_n289(x) + end +end + +def fun_l0_n438(x) + if (x < 1) + fun_l1_n69(x) + else + fun_l1_n549(x) + end +end + +def fun_l0_n439(x) + if (x < 1) + fun_l1_n275(x) + else + fun_l1_n723(x) + end +end + +def fun_l0_n440(x) + if (x < 1) + fun_l1_n469(x) + else + fun_l1_n243(x) + end +end + +def fun_l0_n441(x) + if (x < 1) + fun_l1_n595(x) + else + fun_l1_n624(x) + end +end + +def fun_l0_n442(x) + if (x < 1) + fun_l1_n655(x) + else + fun_l1_n896(x) + end +end + +def fun_l0_n443(x) + if (x < 1) + fun_l1_n926(x) + else + fun_l1_n503(x) + end +end + +def fun_l0_n444(x) + if (x < 1) + fun_l1_n875(x) + else + fun_l1_n110(x) + end +end + +def fun_l0_n445(x) + if (x < 1) + fun_l1_n832(x) + else + fun_l1_n154(x) + end +end + +def fun_l0_n446(x) + if (x < 1) + fun_l1_n538(x) + else + fun_l1_n75(x) + end +end + +def fun_l0_n447(x) + if (x < 1) + fun_l1_n183(x) + else + fun_l1_n718(x) + end +end + +def fun_l0_n448(x) + if (x < 1) + fun_l1_n680(x) + else + fun_l1_n93(x) + end +end + +def fun_l0_n449(x) + if (x < 1) + fun_l1_n147(x) + else + fun_l1_n924(x) + end +end + +def fun_l0_n450(x) + if (x < 1) + fun_l1_n702(x) + else + fun_l1_n830(x) + end +end + +def fun_l0_n451(x) + if (x < 1) + fun_l1_n750(x) + else + fun_l1_n447(x) + end +end + +def fun_l0_n452(x) + if (x < 1) + fun_l1_n520(x) + else + fun_l1_n69(x) + end +end + +def fun_l0_n453(x) + if (x < 1) + fun_l1_n132(x) + else + fun_l1_n877(x) + end +end + +def fun_l0_n454(x) + if (x < 1) + fun_l1_n247(x) + else + fun_l1_n69(x) + end +end + +def fun_l0_n455(x) + if (x < 1) + fun_l1_n180(x) + else + fun_l1_n645(x) + end +end + +def fun_l0_n456(x) + if (x < 1) + fun_l1_n658(x) + else + fun_l1_n487(x) + end +end + +def fun_l0_n457(x) + if (x < 1) + fun_l1_n276(x) + else + fun_l1_n528(x) + end +end + +def fun_l0_n458(x) + if (x < 1) + fun_l1_n30(x) + else + fun_l1_n456(x) + end +end + +def fun_l0_n459(x) + if (x < 1) + fun_l1_n986(x) + else + fun_l1_n552(x) + end +end + +def fun_l0_n460(x) + if (x < 1) + fun_l1_n874(x) + else + fun_l1_n396(x) + end +end + +def fun_l0_n461(x) + if (x < 1) + fun_l1_n524(x) + else + fun_l1_n335(x) + end +end + +def fun_l0_n462(x) + if (x < 1) + fun_l1_n471(x) + else + fun_l1_n578(x) + end +end + +def fun_l0_n463(x) + if (x < 1) + fun_l1_n173(x) + else + fun_l1_n456(x) + end +end + +def fun_l0_n464(x) + if (x < 1) + fun_l1_n872(x) + else + fun_l1_n745(x) + end +end + +def fun_l0_n465(x) + if (x < 1) + fun_l1_n387(x) + else + fun_l1_n776(x) + end +end + +def fun_l0_n466(x) + if (x < 1) + fun_l1_n119(x) + else + fun_l1_n428(x) + end +end + +def fun_l0_n467(x) + if (x < 1) + fun_l1_n416(x) + else + fun_l1_n738(x) + end +end + +def fun_l0_n468(x) + if (x < 1) + fun_l1_n762(x) + else + fun_l1_n694(x) + end +end + +def fun_l0_n469(x) + if (x < 1) + fun_l1_n27(x) + else + fun_l1_n562(x) + end +end + +def fun_l0_n470(x) + if (x < 1) + fun_l1_n12(x) + else + fun_l1_n812(x) + end +end + +def fun_l0_n471(x) + if (x < 1) + fun_l1_n865(x) + else + fun_l1_n94(x) + end +end + +def fun_l0_n472(x) + if (x < 1) + fun_l1_n346(x) + else + fun_l1_n39(x) + end +end + +def fun_l0_n473(x) + if (x < 1) + fun_l1_n309(x) + else + fun_l1_n158(x) + end +end + +def fun_l0_n474(x) + if (x < 1) + fun_l1_n828(x) + else + fun_l1_n229(x) + end +end + +def fun_l0_n475(x) + if (x < 1) + fun_l1_n554(x) + else + fun_l1_n121(x) + end +end + +def fun_l0_n476(x) + if (x < 1) + fun_l1_n818(x) + else + fun_l1_n390(x) + end +end + +def fun_l0_n477(x) + if (x < 1) + fun_l1_n88(x) + else + fun_l1_n73(x) + end +end + +def fun_l0_n478(x) + if (x < 1) + fun_l1_n709(x) + else + fun_l1_n680(x) + end +end + +def fun_l0_n479(x) + if (x < 1) + fun_l1_n271(x) + else + fun_l1_n439(x) + end +end + +def fun_l0_n480(x) + if (x < 1) + fun_l1_n493(x) + else + fun_l1_n562(x) + end +end + +def fun_l0_n481(x) + if (x < 1) + fun_l1_n642(x) + else + fun_l1_n221(x) + end +end + +def fun_l0_n482(x) + if (x < 1) + fun_l1_n838(x) + else + fun_l1_n345(x) + end +end + +def fun_l0_n483(x) + if (x < 1) + fun_l1_n411(x) + else + fun_l1_n266(x) + end +end + +def fun_l0_n484(x) + if (x < 1) + fun_l1_n916(x) + else + fun_l1_n791(x) + end +end + +def fun_l0_n485(x) + if (x < 1) + fun_l1_n392(x) + else + fun_l1_n68(x) + end +end + +def fun_l0_n486(x) + if (x < 1) + fun_l1_n834(x) + else + fun_l1_n783(x) + end +end + +def fun_l0_n487(x) + if (x < 1) + fun_l1_n398(x) + else + fun_l1_n627(x) + end +end + +def fun_l0_n488(x) + if (x < 1) + fun_l1_n904(x) + else + fun_l1_n167(x) + end +end + +def fun_l0_n489(x) + if (x < 1) + fun_l1_n54(x) + else + fun_l1_n28(x) + end +end + +def fun_l0_n490(x) + if (x < 1) + fun_l1_n24(x) + else + fun_l1_n9(x) + end +end + +def fun_l0_n491(x) + if (x < 1) + fun_l1_n497(x) + else + fun_l1_n297(x) + end +end + +def fun_l0_n492(x) + if (x < 1) + fun_l1_n951(x) + else + fun_l1_n534(x) + end +end + +def fun_l0_n493(x) + if (x < 1) + fun_l1_n213(x) + else + fun_l1_n808(x) + end +end + +def fun_l0_n494(x) + if (x < 1) + fun_l1_n904(x) + else + fun_l1_n94(x) + end +end + +def fun_l0_n495(x) + if (x < 1) + fun_l1_n799(x) + else + fun_l1_n817(x) + end +end + +def fun_l0_n496(x) + if (x < 1) + fun_l1_n45(x) + else + fun_l1_n860(x) + end +end + +def fun_l0_n497(x) + if (x < 1) + fun_l1_n482(x) + else + fun_l1_n783(x) + end +end + +def fun_l0_n498(x) + if (x < 1) + fun_l1_n78(x) + else + fun_l1_n785(x) + end +end + +def fun_l0_n499(x) + if (x < 1) + fun_l1_n174(x) + else + fun_l1_n87(x) + end +end + +def fun_l0_n500(x) + if (x < 1) + fun_l1_n608(x) + else + fun_l1_n7(x) + end +end + +def fun_l0_n501(x) + if (x < 1) + fun_l1_n157(x) + else + fun_l1_n718(x) + end +end + +def fun_l0_n502(x) + if (x < 1) + fun_l1_n823(x) + else + fun_l1_n549(x) + end +end + +def fun_l0_n503(x) + if (x < 1) + fun_l1_n13(x) + else + fun_l1_n502(x) + end +end + +def fun_l0_n504(x) + if (x < 1) + fun_l1_n119(x) + else + fun_l1_n191(x) + end +end + +def fun_l0_n505(x) + if (x < 1) + fun_l1_n503(x) + else + fun_l1_n974(x) + end +end + +def fun_l0_n506(x) + if (x < 1) + fun_l1_n893(x) + else + fun_l1_n582(x) + end +end + +def fun_l0_n507(x) + if (x < 1) + fun_l1_n403(x) + else + fun_l1_n228(x) + end +end + +def fun_l0_n508(x) + if (x < 1) + fun_l1_n640(x) + else + fun_l1_n362(x) + end +end + +def fun_l0_n509(x) + if (x < 1) + fun_l1_n629(x) + else + fun_l1_n824(x) + end +end + +def fun_l0_n510(x) + if (x < 1) + fun_l1_n84(x) + else + fun_l1_n330(x) + end +end + +def fun_l0_n511(x) + if (x < 1) + fun_l1_n91(x) + else + fun_l1_n523(x) + end +end + +def fun_l0_n512(x) + if (x < 1) + fun_l1_n696(x) + else + fun_l1_n126(x) + end +end + +def fun_l0_n513(x) + if (x < 1) + fun_l1_n905(x) + else + fun_l1_n496(x) + end +end + +def fun_l0_n514(x) + if (x < 1) + fun_l1_n766(x) + else + fun_l1_n523(x) + end +end + +def fun_l0_n515(x) + if (x < 1) + fun_l1_n798(x) + else + fun_l1_n698(x) + end +end + +def fun_l0_n516(x) + if (x < 1) + fun_l1_n124(x) + else + fun_l1_n356(x) + end +end + +def fun_l0_n517(x) + if (x < 1) + fun_l1_n289(x) + else + fun_l1_n782(x) + end +end + +def fun_l0_n518(x) + if (x < 1) + fun_l1_n483(x) + else + fun_l1_n586(x) + end +end + +def fun_l0_n519(x) + if (x < 1) + fun_l1_n878(x) + else + fun_l1_n37(x) + end +end + +def fun_l0_n520(x) + if (x < 1) + fun_l1_n48(x) + else + fun_l1_n216(x) + end +end + +def fun_l0_n521(x) + if (x < 1) + fun_l1_n805(x) + else + fun_l1_n846(x) + end +end + +def fun_l0_n522(x) + if (x < 1) + fun_l1_n422(x) + else + fun_l1_n190(x) + end +end + +def fun_l0_n523(x) + if (x < 1) + fun_l1_n168(x) + else + fun_l1_n272(x) + end +end + +def fun_l0_n524(x) + if (x < 1) + fun_l1_n766(x) + else + fun_l1_n125(x) + end +end + +def fun_l0_n525(x) + if (x < 1) + fun_l1_n56(x) + else + fun_l1_n224(x) + end +end + +def fun_l0_n526(x) + if (x < 1) + fun_l1_n540(x) + else + fun_l1_n303(x) + end +end + +def fun_l0_n527(x) + if (x < 1) + fun_l1_n846(x) + else + fun_l1_n955(x) + end +end + +def fun_l0_n528(x) + if (x < 1) + fun_l1_n571(x) + else + fun_l1_n640(x) + end +end + +def fun_l0_n529(x) + if (x < 1) + fun_l1_n650(x) + else + fun_l1_n107(x) + end +end + +def fun_l0_n530(x) + if (x < 1) + fun_l1_n240(x) + else + fun_l1_n139(x) + end +end + +def fun_l0_n531(x) + if (x < 1) + fun_l1_n975(x) + else + fun_l1_n970(x) + end +end + +def fun_l0_n532(x) + if (x < 1) + fun_l1_n753(x) + else + fun_l1_n147(x) + end +end + +def fun_l0_n533(x) + if (x < 1) + fun_l1_n269(x) + else + fun_l1_n292(x) + end +end + +def fun_l0_n534(x) + if (x < 1) + fun_l1_n791(x) + else + fun_l1_n69(x) + end +end + +def fun_l0_n535(x) + if (x < 1) + fun_l1_n469(x) + else + fun_l1_n58(x) + end +end + +def fun_l0_n536(x) + if (x < 1) + fun_l1_n526(x) + else + fun_l1_n265(x) + end +end + +def fun_l0_n537(x) + if (x < 1) + fun_l1_n681(x) + else + fun_l1_n646(x) + end +end + +def fun_l0_n538(x) + if (x < 1) + fun_l1_n377(x) + else + fun_l1_n969(x) + end +end + +def fun_l0_n539(x) + if (x < 1) + fun_l1_n935(x) + else + fun_l1_n556(x) + end +end + +def fun_l0_n540(x) + if (x < 1) + fun_l1_n498(x) + else + fun_l1_n439(x) + end +end + +def fun_l0_n541(x) + if (x < 1) + fun_l1_n451(x) + else + fun_l1_n35(x) + end +end + +def fun_l0_n542(x) + if (x < 1) + fun_l1_n32(x) + else + fun_l1_n795(x) + end +end + +def fun_l0_n543(x) + if (x < 1) + fun_l1_n732(x) + else + fun_l1_n530(x) + end +end + +def fun_l0_n544(x) + if (x < 1) + fun_l1_n176(x) + else + fun_l1_n735(x) + end +end + +def fun_l0_n545(x) + if (x < 1) + fun_l1_n34(x) + else + fun_l1_n989(x) + end +end + +def fun_l0_n546(x) + if (x < 1) + fun_l1_n534(x) + else + fun_l1_n705(x) + end +end + +def fun_l0_n547(x) + if (x < 1) + fun_l1_n997(x) + else + fun_l1_n883(x) + end +end + +def fun_l0_n548(x) + if (x < 1) + fun_l1_n379(x) + else + fun_l1_n33(x) + end +end + +def fun_l0_n549(x) + if (x < 1) + fun_l1_n454(x) + else + fun_l1_n516(x) + end +end + +def fun_l0_n550(x) + if (x < 1) + fun_l1_n678(x) + else + fun_l1_n652(x) + end +end + +def fun_l0_n551(x) + if (x < 1) + fun_l1_n13(x) + else + fun_l1_n864(x) + end +end + +def fun_l0_n552(x) + if (x < 1) + fun_l1_n178(x) + else + fun_l1_n996(x) + end +end + +def fun_l0_n553(x) + if (x < 1) + fun_l1_n865(x) + else + fun_l1_n703(x) + end +end + +def fun_l0_n554(x) + if (x < 1) + fun_l1_n306(x) + else + fun_l1_n24(x) + end +end + +def fun_l0_n555(x) + if (x < 1) + fun_l1_n110(x) + else + fun_l1_n123(x) + end +end + +def fun_l0_n556(x) + if (x < 1) + fun_l1_n367(x) + else + fun_l1_n117(x) + end +end + +def fun_l0_n557(x) + if (x < 1) + fun_l1_n967(x) + else + fun_l1_n662(x) + end +end + +def fun_l0_n558(x) + if (x < 1) + fun_l1_n68(x) + else + fun_l1_n47(x) + end +end + +def fun_l0_n559(x) + if (x < 1) + fun_l1_n154(x) + else + fun_l1_n344(x) + end +end + +def fun_l0_n560(x) + if (x < 1) + fun_l1_n835(x) + else + fun_l1_n577(x) + end +end + +def fun_l0_n561(x) + if (x < 1) + fun_l1_n393(x) + else + fun_l1_n419(x) + end +end + +def fun_l0_n562(x) + if (x < 1) + fun_l1_n632(x) + else + fun_l1_n630(x) + end +end + +def fun_l0_n563(x) + if (x < 1) + fun_l1_n9(x) + else + fun_l1_n109(x) + end +end + +def fun_l0_n564(x) + if (x < 1) + fun_l1_n65(x) + else + fun_l1_n123(x) + end +end + +def fun_l0_n565(x) + if (x < 1) + fun_l1_n156(x) + else + fun_l1_n921(x) + end +end + +def fun_l0_n566(x) + if (x < 1) + fun_l1_n88(x) + else + fun_l1_n743(x) + end +end + +def fun_l0_n567(x) + if (x < 1) + fun_l1_n92(x) + else + fun_l1_n27(x) + end +end + +def fun_l0_n568(x) + if (x < 1) + fun_l1_n462(x) + else + fun_l1_n988(x) + end +end + +def fun_l0_n569(x) + if (x < 1) + fun_l1_n241(x) + else + fun_l1_n199(x) + end +end + +def fun_l0_n570(x) + if (x < 1) + fun_l1_n429(x) + else + fun_l1_n530(x) + end +end + +def fun_l0_n571(x) + if (x < 1) + fun_l1_n412(x) + else + fun_l1_n530(x) + end +end + +def fun_l0_n572(x) + if (x < 1) + fun_l1_n100(x) + else + fun_l1_n270(x) + end +end + +def fun_l0_n573(x) + if (x < 1) + fun_l1_n326(x) + else + fun_l1_n191(x) + end +end + +def fun_l0_n574(x) + if (x < 1) + fun_l1_n151(x) + else + fun_l1_n354(x) + end +end + +def fun_l0_n575(x) + if (x < 1) + fun_l1_n322(x) + else + fun_l1_n534(x) + end +end + +def fun_l0_n576(x) + if (x < 1) + fun_l1_n995(x) + else + fun_l1_n658(x) + end +end + +def fun_l0_n577(x) + if (x < 1) + fun_l1_n193(x) + else + fun_l1_n444(x) + end +end + +def fun_l0_n578(x) + if (x < 1) + fun_l1_n527(x) + else + fun_l1_n517(x) + end +end + +def fun_l0_n579(x) + if (x < 1) + fun_l1_n885(x) + else + fun_l1_n218(x) + end +end + +def fun_l0_n580(x) + if (x < 1) + fun_l1_n789(x) + else + fun_l1_n711(x) + end +end + +def fun_l0_n581(x) + if (x < 1) + fun_l1_n848(x) + else + fun_l1_n321(x) + end +end + +def fun_l0_n582(x) + if (x < 1) + fun_l1_n389(x) + else + fun_l1_n360(x) + end +end + +def fun_l0_n583(x) + if (x < 1) + fun_l1_n319(x) + else + fun_l1_n972(x) + end +end + +def fun_l0_n584(x) + if (x < 1) + fun_l1_n421(x) + else + fun_l1_n659(x) + end +end + +def fun_l0_n585(x) + if (x < 1) + fun_l1_n462(x) + else + fun_l1_n302(x) + end +end + +def fun_l0_n586(x) + if (x < 1) + fun_l1_n122(x) + else + fun_l1_n50(x) + end +end + +def fun_l0_n587(x) + if (x < 1) + fun_l1_n795(x) + else + fun_l1_n622(x) + end +end + +def fun_l0_n588(x) + if (x < 1) + fun_l1_n719(x) + else + fun_l1_n390(x) + end +end + +def fun_l0_n589(x) + if (x < 1) + fun_l1_n916(x) + else + fun_l1_n925(x) + end +end + +def fun_l0_n590(x) + if (x < 1) + fun_l1_n541(x) + else + fun_l1_n139(x) + end +end + +def fun_l0_n591(x) + if (x < 1) + fun_l1_n610(x) + else + fun_l1_n509(x) + end +end + +def fun_l0_n592(x) + if (x < 1) + fun_l1_n217(x) + else + fun_l1_n452(x) + end +end + +def fun_l0_n593(x) + if (x < 1) + fun_l1_n456(x) + else + fun_l1_n888(x) + end +end + +def fun_l0_n594(x) + if (x < 1) + fun_l1_n987(x) + else + fun_l1_n462(x) + end +end + +def fun_l0_n595(x) + if (x < 1) + fun_l1_n571(x) + else + fun_l1_n429(x) + end +end + +def fun_l0_n596(x) + if (x < 1) + fun_l1_n751(x) + else + fun_l1_n773(x) + end +end + +def fun_l0_n597(x) + if (x < 1) + fun_l1_n181(x) + else + fun_l1_n340(x) + end +end + +def fun_l0_n598(x) + if (x < 1) + fun_l1_n6(x) + else + fun_l1_n39(x) + end +end + +def fun_l0_n599(x) + if (x < 1) + fun_l1_n814(x) + else + fun_l1_n320(x) + end +end + +def fun_l0_n600(x) + if (x < 1) + fun_l1_n931(x) + else + fun_l1_n535(x) + end +end + +def fun_l0_n601(x) + if (x < 1) + fun_l1_n99(x) + else + fun_l1_n154(x) + end +end + +def fun_l0_n602(x) + if (x < 1) + fun_l1_n981(x) + else + fun_l1_n817(x) + end +end + +def fun_l0_n603(x) + if (x < 1) + fun_l1_n975(x) + else + fun_l1_n364(x) + end +end + +def fun_l0_n604(x) + if (x < 1) + fun_l1_n112(x) + else + fun_l1_n226(x) + end +end + +def fun_l0_n605(x) + if (x < 1) + fun_l1_n995(x) + else + fun_l1_n95(x) + end +end + +def fun_l0_n606(x) + if (x < 1) + fun_l1_n930(x) + else + fun_l1_n77(x) + end +end + +def fun_l0_n607(x) + if (x < 1) + fun_l1_n899(x) + else + fun_l1_n957(x) + end +end + +def fun_l0_n608(x) + if (x < 1) + fun_l1_n844(x) + else + fun_l1_n119(x) + end +end + +def fun_l0_n609(x) + if (x < 1) + fun_l1_n861(x) + else + fun_l1_n74(x) + end +end + +def fun_l0_n610(x) + if (x < 1) + fun_l1_n521(x) + else + fun_l1_n46(x) + end +end + +def fun_l0_n611(x) + if (x < 1) + fun_l1_n258(x) + else + fun_l1_n208(x) + end +end + +def fun_l0_n612(x) + if (x < 1) + fun_l1_n620(x) + else + fun_l1_n427(x) + end +end + +def fun_l0_n613(x) + if (x < 1) + fun_l1_n583(x) + else + fun_l1_n400(x) + end +end + +def fun_l0_n614(x) + if (x < 1) + fun_l1_n933(x) + else + fun_l1_n810(x) + end +end + +def fun_l0_n615(x) + if (x < 1) + fun_l1_n154(x) + else + fun_l1_n879(x) + end +end + +def fun_l0_n616(x) + if (x < 1) + fun_l1_n715(x) + else + fun_l1_n862(x) + end +end + +def fun_l0_n617(x) + if (x < 1) + fun_l1_n178(x) + else + fun_l1_n285(x) + end +end + +def fun_l0_n618(x) + if (x < 1) + fun_l1_n458(x) + else + fun_l1_n217(x) + end +end + +def fun_l0_n619(x) + if (x < 1) + fun_l1_n655(x) + else + fun_l1_n405(x) + end +end + +def fun_l0_n620(x) + if (x < 1) + fun_l1_n470(x) + else + fun_l1_n306(x) + end +end + +def fun_l0_n621(x) + if (x < 1) + fun_l1_n755(x) + else + fun_l1_n425(x) + end +end + +def fun_l0_n622(x) + if (x < 1) + fun_l1_n426(x) + else + fun_l1_n737(x) + end +end + +def fun_l0_n623(x) + if (x < 1) + fun_l1_n79(x) + else + fun_l1_n55(x) + end +end + +def fun_l0_n624(x) + if (x < 1) + fun_l1_n437(x) + else + fun_l1_n79(x) + end +end + +def fun_l0_n625(x) + if (x < 1) + fun_l1_n691(x) + else + fun_l1_n198(x) + end +end + +def fun_l0_n626(x) + if (x < 1) + fun_l1_n703(x) + else + fun_l1_n585(x) + end +end + +def fun_l0_n627(x) + if (x < 1) + fun_l1_n966(x) + else + fun_l1_n425(x) + end +end + +def fun_l0_n628(x) + if (x < 1) + fun_l1_n516(x) + else + fun_l1_n744(x) + end +end + +def fun_l0_n629(x) + if (x < 1) + fun_l1_n40(x) + else + fun_l1_n942(x) + end +end + +def fun_l0_n630(x) + if (x < 1) + fun_l1_n528(x) + else + fun_l1_n440(x) + end +end + +def fun_l0_n631(x) + if (x < 1) + fun_l1_n139(x) + else + fun_l1_n403(x) + end +end + +def fun_l0_n632(x) + if (x < 1) + fun_l1_n421(x) + else + fun_l1_n510(x) + end +end + +def fun_l0_n633(x) + if (x < 1) + fun_l1_n718(x) + else + fun_l1_n388(x) + end +end + +def fun_l0_n634(x) + if (x < 1) + fun_l1_n67(x) + else + fun_l1_n342(x) + end +end + +def fun_l0_n635(x) + if (x < 1) + fun_l1_n943(x) + else + fun_l1_n941(x) + end +end + +def fun_l0_n636(x) + if (x < 1) + fun_l1_n170(x) + else + fun_l1_n527(x) + end +end + +def fun_l0_n637(x) + if (x < 1) + fun_l1_n336(x) + else + fun_l1_n496(x) + end +end + +def fun_l0_n638(x) + if (x < 1) + fun_l1_n730(x) + else + fun_l1_n10(x) + end +end + +def fun_l0_n639(x) + if (x < 1) + fun_l1_n939(x) + else + fun_l1_n940(x) + end +end + +def fun_l0_n640(x) + if (x < 1) + fun_l1_n73(x) + else + fun_l1_n439(x) + end +end + +def fun_l0_n641(x) + if (x < 1) + fun_l1_n460(x) + else + fun_l1_n828(x) + end +end + +def fun_l0_n642(x) + if (x < 1) + fun_l1_n90(x) + else + fun_l1_n113(x) + end +end + +def fun_l0_n643(x) + if (x < 1) + fun_l1_n972(x) + else + fun_l1_n15(x) + end +end + +def fun_l0_n644(x) + if (x < 1) + fun_l1_n417(x) + else + fun_l1_n228(x) + end +end + +def fun_l0_n645(x) + if (x < 1) + fun_l1_n876(x) + else + fun_l1_n152(x) + end +end + +def fun_l0_n646(x) + if (x < 1) + fun_l1_n404(x) + else + fun_l1_n147(x) + end +end + +def fun_l0_n647(x) + if (x < 1) + fun_l1_n802(x) + else + fun_l1_n824(x) + end +end + +def fun_l0_n648(x) + if (x < 1) + fun_l1_n771(x) + else + fun_l1_n421(x) + end +end + +def fun_l0_n649(x) + if (x < 1) + fun_l1_n807(x) + else + fun_l1_n955(x) + end +end + +def fun_l0_n650(x) + if (x < 1) + fun_l1_n221(x) + else + fun_l1_n367(x) + end +end + +def fun_l0_n651(x) + if (x < 1) + fun_l1_n955(x) + else + fun_l1_n401(x) + end +end + +def fun_l0_n652(x) + if (x < 1) + fun_l1_n203(x) + else + fun_l1_n909(x) + end +end + +def fun_l0_n653(x) + if (x < 1) + fun_l1_n154(x) + else + fun_l1_n535(x) + end +end + +def fun_l0_n654(x) + if (x < 1) + fun_l1_n420(x) + else + fun_l1_n393(x) + end +end + +def fun_l0_n655(x) + if (x < 1) + fun_l1_n232(x) + else + fun_l1_n981(x) + end +end + +def fun_l0_n656(x) + if (x < 1) + fun_l1_n873(x) + else + fun_l1_n510(x) + end +end + +def fun_l0_n657(x) + if (x < 1) + fun_l1_n919(x) + else + fun_l1_n706(x) + end +end + +def fun_l0_n658(x) + if (x < 1) + fun_l1_n429(x) + else + fun_l1_n112(x) + end +end + +def fun_l0_n659(x) + if (x < 1) + fun_l1_n547(x) + else + fun_l1_n712(x) + end +end + +def fun_l0_n660(x) + if (x < 1) + fun_l1_n498(x) + else + fun_l1_n695(x) + end +end + +def fun_l0_n661(x) + if (x < 1) + fun_l1_n801(x) + else + fun_l1_n659(x) + end +end + +def fun_l0_n662(x) + if (x < 1) + fun_l1_n796(x) + else + fun_l1_n229(x) + end +end + +def fun_l0_n663(x) + if (x < 1) + fun_l1_n11(x) + else + fun_l1_n620(x) + end +end + +def fun_l0_n664(x) + if (x < 1) + fun_l1_n652(x) + else + fun_l1_n416(x) + end +end + +def fun_l0_n665(x) + if (x < 1) + fun_l1_n655(x) + else + fun_l1_n331(x) + end +end + +def fun_l0_n666(x) + if (x < 1) + fun_l1_n846(x) + else + fun_l1_n1(x) + end +end + +def fun_l0_n667(x) + if (x < 1) + fun_l1_n55(x) + else + fun_l1_n32(x) + end +end + +def fun_l0_n668(x) + if (x < 1) + fun_l1_n241(x) + else + fun_l1_n627(x) + end +end + +def fun_l0_n669(x) + if (x < 1) + fun_l1_n234(x) + else + fun_l1_n996(x) + end +end + +def fun_l0_n670(x) + if (x < 1) + fun_l1_n235(x) + else + fun_l1_n761(x) + end +end + +def fun_l0_n671(x) + if (x < 1) + fun_l1_n990(x) + else + fun_l1_n236(x) + end +end + +def fun_l0_n672(x) + if (x < 1) + fun_l1_n970(x) + else + fun_l1_n595(x) + end +end + +def fun_l0_n673(x) + if (x < 1) + fun_l1_n294(x) + else + fun_l1_n440(x) + end +end + +def fun_l0_n674(x) + if (x < 1) + fun_l1_n597(x) + else + fun_l1_n956(x) + end +end + +def fun_l0_n675(x) + if (x < 1) + fun_l1_n942(x) + else + fun_l1_n352(x) + end +end + +def fun_l0_n676(x) + if (x < 1) + fun_l1_n309(x) + else + fun_l1_n294(x) + end +end + +def fun_l0_n677(x) + if (x < 1) + fun_l1_n752(x) + else + fun_l1_n447(x) + end +end + +def fun_l0_n678(x) + if (x < 1) + fun_l1_n890(x) + else + fun_l1_n497(x) + end +end + +def fun_l0_n679(x) + if (x < 1) + fun_l1_n808(x) + else + fun_l1_n881(x) + end +end + +def fun_l0_n680(x) + if (x < 1) + fun_l1_n966(x) + else + fun_l1_n460(x) + end +end + +def fun_l0_n681(x) + if (x < 1) + fun_l1_n104(x) + else + fun_l1_n262(x) + end +end + +def fun_l0_n682(x) + if (x < 1) + fun_l1_n425(x) + else + fun_l1_n177(x) + end +end + +def fun_l0_n683(x) + if (x < 1) + fun_l1_n15(x) + else + fun_l1_n458(x) + end +end + +def fun_l0_n684(x) + if (x < 1) + fun_l1_n760(x) + else + fun_l1_n997(x) + end +end + +def fun_l0_n685(x) + if (x < 1) + fun_l1_n496(x) + else + fun_l1_n223(x) + end +end + +def fun_l0_n686(x) + if (x < 1) + fun_l1_n363(x) + else + fun_l1_n543(x) + end +end + +def fun_l0_n687(x) + if (x < 1) + fun_l1_n758(x) + else + fun_l1_n473(x) + end +end + +def fun_l0_n688(x) + if (x < 1) + fun_l1_n854(x) + else + fun_l1_n999(x) + end +end + +def fun_l0_n689(x) + if (x < 1) + fun_l1_n336(x) + else + fun_l1_n388(x) + end +end + +def fun_l0_n690(x) + if (x < 1) + fun_l1_n930(x) + else + fun_l1_n478(x) + end +end + +def fun_l0_n691(x) + if (x < 1) + fun_l1_n423(x) + else + fun_l1_n83(x) + end +end + +def fun_l0_n692(x) + if (x < 1) + fun_l1_n797(x) + else + fun_l1_n427(x) + end +end + +def fun_l0_n693(x) + if (x < 1) + fun_l1_n907(x) + else + fun_l1_n1(x) + end +end + +def fun_l0_n694(x) + if (x < 1) + fun_l1_n601(x) + else + fun_l1_n809(x) + end +end + +def fun_l0_n695(x) + if (x < 1) + fun_l1_n252(x) + else + fun_l1_n233(x) + end +end + +def fun_l0_n696(x) + if (x < 1) + fun_l1_n36(x) + else + fun_l1_n796(x) + end +end + +def fun_l0_n697(x) + if (x < 1) + fun_l1_n893(x) + else + fun_l1_n926(x) + end +end + +def fun_l0_n698(x) + if (x < 1) + fun_l1_n511(x) + else + fun_l1_n88(x) + end +end + +def fun_l0_n699(x) + if (x < 1) + fun_l1_n573(x) + else + fun_l1_n718(x) + end +end + +def fun_l0_n700(x) + if (x < 1) + fun_l1_n475(x) + else + fun_l1_n539(x) + end +end + +def fun_l0_n701(x) + if (x < 1) + fun_l1_n238(x) + else + fun_l1_n253(x) + end +end + +def fun_l0_n702(x) + if (x < 1) + fun_l1_n731(x) + else + fun_l1_n225(x) + end +end + +def fun_l0_n703(x) + if (x < 1) + fun_l1_n594(x) + else + fun_l1_n16(x) + end +end + +def fun_l0_n704(x) + if (x < 1) + fun_l1_n732(x) + else + fun_l1_n354(x) + end +end + +def fun_l0_n705(x) + if (x < 1) + fun_l1_n812(x) + else + fun_l1_n218(x) + end +end + +def fun_l0_n706(x) + if (x < 1) + fun_l1_n731(x) + else + fun_l1_n343(x) + end +end + +def fun_l0_n707(x) + if (x < 1) + fun_l1_n134(x) + else + fun_l1_n752(x) + end +end + +def fun_l0_n708(x) + if (x < 1) + fun_l1_n899(x) + else + fun_l1_n854(x) + end +end + +def fun_l0_n709(x) + if (x < 1) + fun_l1_n120(x) + else + fun_l1_n193(x) + end +end + +def fun_l0_n710(x) + if (x < 1) + fun_l1_n22(x) + else + fun_l1_n254(x) + end +end + +def fun_l0_n711(x) + if (x < 1) + fun_l1_n527(x) + else + fun_l1_n921(x) + end +end + +def fun_l0_n712(x) + if (x < 1) + fun_l1_n820(x) + else + fun_l1_n96(x) + end +end + +def fun_l0_n713(x) + if (x < 1) + fun_l1_n145(x) + else + fun_l1_n226(x) + end +end + +def fun_l0_n714(x) + if (x < 1) + fun_l1_n643(x) + else + fun_l1_n407(x) + end +end + +def fun_l0_n715(x) + if (x < 1) + fun_l1_n349(x) + else + fun_l1_n450(x) + end +end + +def fun_l0_n716(x) + if (x < 1) + fun_l1_n954(x) + else + fun_l1_n707(x) + end +end + +def fun_l0_n717(x) + if (x < 1) + fun_l1_n723(x) + else + fun_l1_n762(x) + end +end + +def fun_l0_n718(x) + if (x < 1) + fun_l1_n299(x) + else + fun_l1_n332(x) + end +end + +def fun_l0_n719(x) + if (x < 1) + fun_l1_n493(x) + else + fun_l1_n521(x) + end +end + +def fun_l0_n720(x) + if (x < 1) + fun_l1_n39(x) + else + fun_l1_n17(x) + end +end + +def fun_l0_n721(x) + if (x < 1) + fun_l1_n297(x) + else + fun_l1_n800(x) + end +end + +def fun_l0_n722(x) + if (x < 1) + fun_l1_n758(x) + else + fun_l1_n135(x) + end +end + +def fun_l0_n723(x) + if (x < 1) + fun_l1_n471(x) + else + fun_l1_n351(x) + end +end + +def fun_l0_n724(x) + if (x < 1) + fun_l1_n381(x) + else + fun_l1_n281(x) + end +end + +def fun_l0_n725(x) + if (x < 1) + fun_l1_n624(x) + else + fun_l1_n162(x) + end +end + +def fun_l0_n726(x) + if (x < 1) + fun_l1_n143(x) + else + fun_l1_n417(x) + end +end + +def fun_l0_n727(x) + if (x < 1) + fun_l1_n623(x) + else + fun_l1_n19(x) + end +end + +def fun_l0_n728(x) + if (x < 1) + fun_l1_n687(x) + else + fun_l1_n574(x) + end +end + +def fun_l0_n729(x) + if (x < 1) + fun_l1_n225(x) + else + fun_l1_n871(x) + end +end + +def fun_l0_n730(x) + if (x < 1) + fun_l1_n830(x) + else + fun_l1_n273(x) + end +end + +def fun_l0_n731(x) + if (x < 1) + fun_l1_n95(x) + else + fun_l1_n864(x) + end +end + +def fun_l0_n732(x) + if (x < 1) + fun_l1_n902(x) + else + fun_l1_n522(x) + end +end + +def fun_l0_n733(x) + if (x < 1) + fun_l1_n564(x) + else + fun_l1_n55(x) + end +end + +def fun_l0_n734(x) + if (x < 1) + fun_l1_n481(x) + else + fun_l1_n880(x) + end +end + +def fun_l0_n735(x) + if (x < 1) + fun_l1_n87(x) + else + fun_l1_n693(x) + end +end + +def fun_l0_n736(x) + if (x < 1) + fun_l1_n72(x) + else + fun_l1_n396(x) + end +end + +def fun_l0_n737(x) + if (x < 1) + fun_l1_n920(x) + else + fun_l1_n852(x) + end +end + +def fun_l0_n738(x) + if (x < 1) + fun_l1_n342(x) + else + fun_l1_n959(x) + end +end + +def fun_l0_n739(x) + if (x < 1) + fun_l1_n682(x) + else + fun_l1_n714(x) + end +end + +def fun_l0_n740(x) + if (x < 1) + fun_l1_n264(x) + else + fun_l1_n325(x) + end +end + +def fun_l0_n741(x) + if (x < 1) + fun_l1_n621(x) + else + fun_l1_n339(x) + end +end + +def fun_l0_n742(x) + if (x < 1) + fun_l1_n46(x) + else + fun_l1_n766(x) + end +end + +def fun_l0_n743(x) + if (x < 1) + fun_l1_n333(x) + else + fun_l1_n353(x) + end +end + +def fun_l0_n744(x) + if (x < 1) + fun_l1_n930(x) + else + fun_l1_n964(x) + end +end + +def fun_l0_n745(x) + if (x < 1) + fun_l1_n785(x) + else + fun_l1_n114(x) + end +end + +def fun_l0_n746(x) + if (x < 1) + fun_l1_n402(x) + else + fun_l1_n803(x) + end +end + +def fun_l0_n747(x) + if (x < 1) + fun_l1_n878(x) + else + fun_l1_n870(x) + end +end + +def fun_l0_n748(x) + if (x < 1) + fun_l1_n683(x) + else + fun_l1_n287(x) + end +end + +def fun_l0_n749(x) + if (x < 1) + fun_l1_n816(x) + else + fun_l1_n849(x) + end +end + +def fun_l0_n750(x) + if (x < 1) + fun_l1_n523(x) + else + fun_l1_n555(x) + end +end + +def fun_l0_n751(x) + if (x < 1) + fun_l1_n270(x) + else + fun_l1_n660(x) + end +end + +def fun_l0_n752(x) + if (x < 1) + fun_l1_n786(x) + else + fun_l1_n749(x) + end +end + +def fun_l0_n753(x) + if (x < 1) + fun_l1_n567(x) + else + fun_l1_n325(x) + end +end + +def fun_l0_n754(x) + if (x < 1) + fun_l1_n502(x) + else + fun_l1_n610(x) + end +end + +def fun_l0_n755(x) + if (x < 1) + fun_l1_n336(x) + else + fun_l1_n354(x) + end +end + +def fun_l0_n756(x) + if (x < 1) + fun_l1_n435(x) + else + fun_l1_n484(x) + end +end + +def fun_l0_n757(x) + if (x < 1) + fun_l1_n849(x) + else + fun_l1_n42(x) + end +end + +def fun_l0_n758(x) + if (x < 1) + fun_l1_n294(x) + else + fun_l1_n483(x) + end +end + +def fun_l0_n759(x) + if (x < 1) + fun_l1_n14(x) + else + fun_l1_n9(x) + end +end + +def fun_l0_n760(x) + if (x < 1) + fun_l1_n860(x) + else + fun_l1_n645(x) + end +end + +def fun_l0_n761(x) + if (x < 1) + fun_l1_n577(x) + else + fun_l1_n0(x) + end +end + +def fun_l0_n762(x) + if (x < 1) + fun_l1_n28(x) + else + fun_l1_n834(x) + end +end + +def fun_l0_n763(x) + if (x < 1) + fun_l1_n147(x) + else + fun_l1_n177(x) + end +end + +def fun_l0_n764(x) + if (x < 1) + fun_l1_n429(x) + else + fun_l1_n646(x) + end +end + +def fun_l0_n765(x) + if (x < 1) + fun_l1_n474(x) + else + fun_l1_n22(x) + end +end + +def fun_l0_n766(x) + if (x < 1) + fun_l1_n264(x) + else + fun_l1_n549(x) + end +end + +def fun_l0_n767(x) + if (x < 1) + fun_l1_n351(x) + else + fun_l1_n813(x) + end +end + +def fun_l0_n768(x) + if (x < 1) + fun_l1_n798(x) + else + fun_l1_n684(x) + end +end + +def fun_l0_n769(x) + if (x < 1) + fun_l1_n202(x) + else + fun_l1_n982(x) + end +end + +def fun_l0_n770(x) + if (x < 1) + fun_l1_n611(x) + else + fun_l1_n52(x) + end +end + +def fun_l0_n771(x) + if (x < 1) + fun_l1_n754(x) + else + fun_l1_n7(x) + end +end + +def fun_l0_n772(x) + if (x < 1) + fun_l1_n98(x) + else + fun_l1_n149(x) + end +end + +def fun_l0_n773(x) + if (x < 1) + fun_l1_n358(x) + else + fun_l1_n603(x) + end +end + +def fun_l0_n774(x) + if (x < 1) + fun_l1_n604(x) + else + fun_l1_n318(x) + end +end + +def fun_l0_n775(x) + if (x < 1) + fun_l1_n831(x) + else + fun_l1_n635(x) + end +end + +def fun_l0_n776(x) + if (x < 1) + fun_l1_n738(x) + else + fun_l1_n563(x) + end +end + +def fun_l0_n777(x) + if (x < 1) + fun_l1_n570(x) + else + fun_l1_n671(x) + end +end + +def fun_l0_n778(x) + if (x < 1) + fun_l1_n945(x) + else + fun_l1_n840(x) + end +end + +def fun_l0_n779(x) + if (x < 1) + fun_l1_n116(x) + else + fun_l1_n942(x) + end +end + +def fun_l0_n780(x) + if (x < 1) + fun_l1_n835(x) + else + fun_l1_n244(x) + end +end + +def fun_l0_n781(x) + if (x < 1) + fun_l1_n427(x) + else + fun_l1_n556(x) + end +end + +def fun_l0_n782(x) + if (x < 1) + fun_l1_n280(x) + else + fun_l1_n926(x) + end +end + +def fun_l0_n783(x) + if (x < 1) + fun_l1_n674(x) + else + fun_l1_n560(x) + end +end + +def fun_l0_n784(x) + if (x < 1) + fun_l1_n978(x) + else + fun_l1_n139(x) + end +end + +def fun_l0_n785(x) + if (x < 1) + fun_l1_n928(x) + else + fun_l1_n11(x) + end +end + +def fun_l0_n786(x) + if (x < 1) + fun_l1_n829(x) + else + fun_l1_n239(x) + end +end + +def fun_l0_n787(x) + if (x < 1) + fun_l1_n732(x) + else + fun_l1_n829(x) + end +end + +def fun_l0_n788(x) + if (x < 1) + fun_l1_n968(x) + else + fun_l1_n596(x) + end +end + +def fun_l0_n789(x) + if (x < 1) + fun_l1_n214(x) + else + fun_l1_n158(x) + end +end + +def fun_l0_n790(x) + if (x < 1) + fun_l1_n566(x) + else + fun_l1_n390(x) + end +end + +def fun_l0_n791(x) + if (x < 1) + fun_l1_n936(x) + else + fun_l1_n31(x) + end +end + +def fun_l0_n792(x) + if (x < 1) + fun_l1_n291(x) + else + fun_l1_n835(x) + end +end + +def fun_l0_n793(x) + if (x < 1) + fun_l1_n300(x) + else + fun_l1_n658(x) + end +end + +def fun_l0_n794(x) + if (x < 1) + fun_l1_n609(x) + else + fun_l1_n156(x) + end +end + +def fun_l0_n795(x) + if (x < 1) + fun_l1_n419(x) + else + fun_l1_n534(x) + end +end + +def fun_l0_n796(x) + if (x < 1) + fun_l1_n401(x) + else + fun_l1_n281(x) + end +end + +def fun_l0_n797(x) + if (x < 1) + fun_l1_n538(x) + else + fun_l1_n905(x) + end +end + +def fun_l0_n798(x) + if (x < 1) + fun_l1_n427(x) + else + fun_l1_n978(x) + end +end + +def fun_l0_n799(x) + if (x < 1) + fun_l1_n851(x) + else + fun_l1_n57(x) + end +end + +def fun_l0_n800(x) + if (x < 1) + fun_l1_n836(x) + else + fun_l1_n306(x) + end +end + +def fun_l0_n801(x) + if (x < 1) + fun_l1_n414(x) + else + fun_l1_n679(x) + end +end + +def fun_l0_n802(x) + if (x < 1) + fun_l1_n645(x) + else + fun_l1_n115(x) + end +end + +def fun_l0_n803(x) + if (x < 1) + fun_l1_n884(x) + else + fun_l1_n801(x) + end +end + +def fun_l0_n804(x) + if (x < 1) + fun_l1_n713(x) + else + fun_l1_n974(x) + end +end + +def fun_l0_n805(x) + if (x < 1) + fun_l1_n301(x) + else + fun_l1_n589(x) + end +end + +def fun_l0_n806(x) + if (x < 1) + fun_l1_n912(x) + else + fun_l1_n766(x) + end +end + +def fun_l0_n807(x) + if (x < 1) + fun_l1_n656(x) + else + fun_l1_n765(x) + end +end + +def fun_l0_n808(x) + if (x < 1) + fun_l1_n517(x) + else + fun_l1_n964(x) + end +end + +def fun_l0_n809(x) + if (x < 1) + fun_l1_n878(x) + else + fun_l1_n69(x) + end +end + +def fun_l0_n810(x) + if (x < 1) + fun_l1_n527(x) + else + fun_l1_n841(x) + end +end + +def fun_l0_n811(x) + if (x < 1) + fun_l1_n564(x) + else + fun_l1_n541(x) + end +end + +def fun_l0_n812(x) + if (x < 1) + fun_l1_n754(x) + else + fun_l1_n681(x) + end +end + +def fun_l0_n813(x) + if (x < 1) + fun_l1_n21(x) + else + fun_l1_n561(x) + end +end + +def fun_l0_n814(x) + if (x < 1) + fun_l1_n547(x) + else + fun_l1_n631(x) + end +end + +def fun_l0_n815(x) + if (x < 1) + fun_l1_n956(x) + else + fun_l1_n889(x) + end +end + +def fun_l0_n816(x) + if (x < 1) + fun_l1_n817(x) + else + fun_l1_n957(x) + end +end + +def fun_l0_n817(x) + if (x < 1) + fun_l1_n622(x) + else + fun_l1_n827(x) + end +end + +def fun_l0_n818(x) + if (x < 1) + fun_l1_n7(x) + else + fun_l1_n208(x) + end +end + +def fun_l0_n819(x) + if (x < 1) + fun_l1_n411(x) + else + fun_l1_n742(x) + end +end + +def fun_l0_n820(x) + if (x < 1) + fun_l1_n676(x) + else + fun_l1_n144(x) + end +end + +def fun_l0_n821(x) + if (x < 1) + fun_l1_n489(x) + else + fun_l1_n963(x) + end +end + +def fun_l0_n822(x) + if (x < 1) + fun_l1_n645(x) + else + fun_l1_n953(x) + end +end + +def fun_l0_n823(x) + if (x < 1) + fun_l1_n974(x) + else + fun_l1_n75(x) + end +end + +def fun_l0_n824(x) + if (x < 1) + fun_l1_n295(x) + else + fun_l1_n249(x) + end +end + +def fun_l0_n825(x) + if (x < 1) + fun_l1_n141(x) + else + fun_l1_n633(x) + end +end + +def fun_l0_n826(x) + if (x < 1) + fun_l1_n317(x) + else + fun_l1_n152(x) + end +end + +def fun_l0_n827(x) + if (x < 1) + fun_l1_n570(x) + else + fun_l1_n377(x) + end +end + +def fun_l0_n828(x) + if (x < 1) + fun_l1_n392(x) + else + fun_l1_n477(x) + end +end + +def fun_l0_n829(x) + if (x < 1) + fun_l1_n277(x) + else + fun_l1_n276(x) + end +end + +def fun_l0_n830(x) + if (x < 1) + fun_l1_n579(x) + else + fun_l1_n648(x) + end +end + +def fun_l0_n831(x) + if (x < 1) + fun_l1_n8(x) + else + fun_l1_n25(x) + end +end + +def fun_l0_n832(x) + if (x < 1) + fun_l1_n724(x) + else + fun_l1_n617(x) + end +end + +def fun_l0_n833(x) + if (x < 1) + fun_l1_n771(x) + else + fun_l1_n619(x) + end +end + +def fun_l0_n834(x) + if (x < 1) + fun_l1_n23(x) + else + fun_l1_n127(x) + end +end + +def fun_l0_n835(x) + if (x < 1) + fun_l1_n322(x) + else + fun_l1_n327(x) + end +end + +def fun_l0_n836(x) + if (x < 1) + fun_l1_n7(x) + else + fun_l1_n600(x) + end +end + +def fun_l0_n837(x) + if (x < 1) + fun_l1_n796(x) + else + fun_l1_n782(x) + end +end + +def fun_l0_n838(x) + if (x < 1) + fun_l1_n937(x) + else + fun_l1_n899(x) + end +end + +def fun_l0_n839(x) + if (x < 1) + fun_l1_n16(x) + else + fun_l1_n349(x) + end +end + +def fun_l0_n840(x) + if (x < 1) + fun_l1_n622(x) + else + fun_l1_n960(x) + end +end + +def fun_l0_n841(x) + if (x < 1) + fun_l1_n295(x) + else + fun_l1_n176(x) + end +end + +def fun_l0_n842(x) + if (x < 1) + fun_l1_n302(x) + else + fun_l1_n639(x) + end +end + +def fun_l0_n843(x) + if (x < 1) + fun_l1_n342(x) + else + fun_l1_n373(x) + end +end + +def fun_l0_n844(x) + if (x < 1) + fun_l1_n5(x) + else + fun_l1_n787(x) + end +end + +def fun_l0_n845(x) + if (x < 1) + fun_l1_n89(x) + else + fun_l1_n951(x) + end +end + +def fun_l0_n846(x) + if (x < 1) + fun_l1_n954(x) + else + fun_l1_n982(x) + end +end + +def fun_l0_n847(x) + if (x < 1) + fun_l1_n971(x) + else + fun_l1_n917(x) + end +end + +def fun_l0_n848(x) + if (x < 1) + fun_l1_n845(x) + else + fun_l1_n343(x) + end +end + +def fun_l0_n849(x) + if (x < 1) + fun_l1_n684(x) + else + fun_l1_n683(x) + end +end + +def fun_l0_n850(x) + if (x < 1) + fun_l1_n670(x) + else + fun_l1_n256(x) + end +end + +def fun_l0_n851(x) + if (x < 1) + fun_l1_n405(x) + else + fun_l1_n123(x) + end +end + +def fun_l0_n852(x) + if (x < 1) + fun_l1_n809(x) + else + fun_l1_n691(x) + end +end + +def fun_l0_n853(x) + if (x < 1) + fun_l1_n937(x) + else + fun_l1_n92(x) + end +end + +def fun_l0_n854(x) + if (x < 1) + fun_l1_n735(x) + else + fun_l1_n948(x) + end +end + +def fun_l0_n855(x) + if (x < 1) + fun_l1_n684(x) + else + fun_l1_n566(x) + end +end + +def fun_l0_n856(x) + if (x < 1) + fun_l1_n508(x) + else + fun_l1_n35(x) + end +end + +def fun_l0_n857(x) + if (x < 1) + fun_l1_n865(x) + else + fun_l1_n930(x) + end +end + +def fun_l0_n858(x) + if (x < 1) + fun_l1_n937(x) + else + fun_l1_n241(x) + end +end + +def fun_l0_n859(x) + if (x < 1) + fun_l1_n938(x) + else + fun_l1_n108(x) + end +end + +def fun_l0_n860(x) + if (x < 1) + fun_l1_n491(x) + else + fun_l1_n119(x) + end +end + +def fun_l0_n861(x) + if (x < 1) + fun_l1_n991(x) + else + fun_l1_n198(x) + end +end + +def fun_l0_n862(x) + if (x < 1) + fun_l1_n846(x) + else + fun_l1_n513(x) + end +end + +def fun_l0_n863(x) + if (x < 1) + fun_l1_n279(x) + else + fun_l1_n229(x) + end +end + +def fun_l0_n864(x) + if (x < 1) + fun_l1_n52(x) + else + fun_l1_n765(x) + end +end + +def fun_l0_n865(x) + if (x < 1) + fun_l1_n35(x) + else + fun_l1_n572(x) + end +end + +def fun_l0_n866(x) + if (x < 1) + fun_l1_n4(x) + else + fun_l1_n645(x) + end +end + +def fun_l0_n867(x) + if (x < 1) + fun_l1_n112(x) + else + fun_l1_n758(x) + end +end + +def fun_l0_n868(x) + if (x < 1) + fun_l1_n924(x) + else + fun_l1_n251(x) + end +end + +def fun_l0_n869(x) + if (x < 1) + fun_l1_n558(x) + else + fun_l1_n818(x) + end +end + +def fun_l0_n870(x) + if (x < 1) + fun_l1_n804(x) + else + fun_l1_n746(x) + end +end + +def fun_l0_n871(x) + if (x < 1) + fun_l1_n111(x) + else + fun_l1_n407(x) + end +end + +def fun_l0_n872(x) + if (x < 1) + fun_l1_n973(x) + else + fun_l1_n384(x) + end +end + +def fun_l0_n873(x) + if (x < 1) + fun_l1_n102(x) + else + fun_l1_n199(x) + end +end + +def fun_l0_n874(x) + if (x < 1) + fun_l1_n735(x) + else + fun_l1_n903(x) + end +end + +def fun_l0_n875(x) + if (x < 1) + fun_l1_n95(x) + else + fun_l1_n720(x) + end +end + +def fun_l0_n876(x) + if (x < 1) + fun_l1_n864(x) + else + fun_l1_n970(x) + end +end + +def fun_l0_n877(x) + if (x < 1) + fun_l1_n729(x) + else + fun_l1_n207(x) + end +end + +def fun_l0_n878(x) + if (x < 1) + fun_l1_n104(x) + else + fun_l1_n209(x) + end +end + +def fun_l0_n879(x) + if (x < 1) + fun_l1_n566(x) + else + fun_l1_n918(x) + end +end + +def fun_l0_n880(x) + if (x < 1) + fun_l1_n457(x) + else + fun_l1_n104(x) + end +end + +def fun_l0_n881(x) + if (x < 1) + fun_l1_n745(x) + else + fun_l1_n476(x) + end +end + +def fun_l0_n882(x) + if (x < 1) + fun_l1_n346(x) + else + fun_l1_n642(x) + end +end + +def fun_l0_n883(x) + if (x < 1) + fun_l1_n78(x) + else + fun_l1_n210(x) + end +end + +def fun_l0_n884(x) + if (x < 1) + fun_l1_n864(x) + else + fun_l1_n439(x) + end +end + +def fun_l0_n885(x) + if (x < 1) + fun_l1_n0(x) + else + fun_l1_n414(x) + end +end + +def fun_l0_n886(x) + if (x < 1) + fun_l1_n217(x) + else + fun_l1_n444(x) + end +end + +def fun_l0_n887(x) + if (x < 1) + fun_l1_n193(x) + else + fun_l1_n788(x) + end +end + +def fun_l0_n888(x) + if (x < 1) + fun_l1_n30(x) + else + fun_l1_n793(x) + end +end + +def fun_l0_n889(x) + if (x < 1) + fun_l1_n222(x) + else + fun_l1_n936(x) + end +end + +def fun_l0_n890(x) + if (x < 1) + fun_l1_n758(x) + else + fun_l1_n52(x) + end +end + +def fun_l0_n891(x) + if (x < 1) + fun_l1_n796(x) + else + fun_l1_n424(x) + end +end + +def fun_l0_n892(x) + if (x < 1) + fun_l1_n627(x) + else + fun_l1_n284(x) + end +end + +def fun_l0_n893(x) + if (x < 1) + fun_l1_n42(x) + else + fun_l1_n193(x) + end +end + +def fun_l0_n894(x) + if (x < 1) + fun_l1_n90(x) + else + fun_l1_n158(x) + end +end + +def fun_l0_n895(x) + if (x < 1) + fun_l1_n878(x) + else + fun_l1_n396(x) + end +end + +def fun_l0_n896(x) + if (x < 1) + fun_l1_n665(x) + else + fun_l1_n731(x) + end +end + +def fun_l0_n897(x) + if (x < 1) + fun_l1_n618(x) + else + fun_l1_n152(x) + end +end + +def fun_l0_n898(x) + if (x < 1) + fun_l1_n64(x) + else + fun_l1_n697(x) + end +end + +def fun_l0_n899(x) + if (x < 1) + fun_l1_n354(x) + else + fun_l1_n657(x) + end +end + +def fun_l0_n900(x) + if (x < 1) + fun_l1_n79(x) + else + fun_l1_n794(x) + end +end + +def fun_l0_n901(x) + if (x < 1) + fun_l1_n623(x) + else + fun_l1_n392(x) + end +end + +def fun_l0_n902(x) + if (x < 1) + fun_l1_n529(x) + else + fun_l1_n708(x) + end +end + +def fun_l0_n903(x) + if (x < 1) + fun_l1_n900(x) + else + fun_l1_n830(x) + end +end + +def fun_l0_n904(x) + if (x < 1) + fun_l1_n424(x) + else + fun_l1_n564(x) + end +end + +def fun_l0_n905(x) + if (x < 1) + fun_l1_n557(x) + else + fun_l1_n612(x) + end +end + +def fun_l0_n906(x) + if (x < 1) + fun_l1_n725(x) + else + fun_l1_n166(x) + end +end + +def fun_l0_n907(x) + if (x < 1) + fun_l1_n534(x) + else + fun_l1_n159(x) + end +end + +def fun_l0_n908(x) + if (x < 1) + fun_l1_n50(x) + else + fun_l1_n201(x) + end +end + +def fun_l0_n909(x) + if (x < 1) + fun_l1_n568(x) + else + fun_l1_n276(x) + end +end + +def fun_l0_n910(x) + if (x < 1) + fun_l1_n0(x) + else + fun_l1_n324(x) + end +end + +def fun_l0_n911(x) + if (x < 1) + fun_l1_n926(x) + else + fun_l1_n524(x) + end +end + +def fun_l0_n912(x) + if (x < 1) + fun_l1_n498(x) + else + fun_l1_n650(x) + end +end + +def fun_l0_n913(x) + if (x < 1) + fun_l1_n593(x) + else + fun_l1_n860(x) + end +end + +def fun_l0_n914(x) + if (x < 1) + fun_l1_n726(x) + else + fun_l1_n440(x) + end +end + +def fun_l0_n915(x) + if (x < 1) + fun_l1_n422(x) + else + fun_l1_n627(x) + end +end + +def fun_l0_n916(x) + if (x < 1) + fun_l1_n346(x) + else + fun_l1_n932(x) + end +end + +def fun_l0_n917(x) + if (x < 1) + fun_l1_n403(x) + else + fun_l1_n58(x) + end +end + +def fun_l0_n918(x) + if (x < 1) + fun_l1_n989(x) + else + fun_l1_n579(x) + end +end + +def fun_l0_n919(x) + if (x < 1) + fun_l1_n933(x) + else + fun_l1_n434(x) + end +end + +def fun_l0_n920(x) + if (x < 1) + fun_l1_n899(x) + else + fun_l1_n377(x) + end +end + +def fun_l0_n921(x) + if (x < 1) + fun_l1_n17(x) + else + fun_l1_n637(x) + end +end + +def fun_l0_n922(x) + if (x < 1) + fun_l1_n551(x) + else + fun_l1_n693(x) + end +end + +def fun_l0_n923(x) + if (x < 1) + fun_l1_n861(x) + else + fun_l1_n248(x) + end +end + +def fun_l0_n924(x) + if (x < 1) + fun_l1_n171(x) + else + fun_l1_n75(x) + end +end + +def fun_l0_n925(x) + if (x < 1) + fun_l1_n233(x) + else + fun_l1_n444(x) + end +end + +def fun_l0_n926(x) + if (x < 1) + fun_l1_n310(x) + else + fun_l1_n941(x) + end +end + +def fun_l0_n927(x) + if (x < 1) + fun_l1_n363(x) + else + fun_l1_n567(x) + end +end + +def fun_l0_n928(x) + if (x < 1) + fun_l1_n632(x) + else + fun_l1_n840(x) + end +end + +def fun_l0_n929(x) + if (x < 1) + fun_l1_n368(x) + else + fun_l1_n713(x) + end +end + +def fun_l0_n930(x) + if (x < 1) + fun_l1_n37(x) + else + fun_l1_n355(x) + end +end + +def fun_l0_n931(x) + if (x < 1) + fun_l1_n907(x) + else + fun_l1_n784(x) + end +end + +def fun_l0_n932(x) + if (x < 1) + fun_l1_n591(x) + else + fun_l1_n844(x) + end +end + +def fun_l0_n933(x) + if (x < 1) + fun_l1_n996(x) + else + fun_l1_n943(x) + end +end + +def fun_l0_n934(x) + if (x < 1) + fun_l1_n631(x) + else + fun_l1_n643(x) + end +end + +def fun_l0_n935(x) + if (x < 1) + fun_l1_n875(x) + else + fun_l1_n89(x) + end +end + +def fun_l0_n936(x) + if (x < 1) + fun_l1_n842(x) + else + fun_l1_n595(x) + end +end + +def fun_l0_n937(x) + if (x < 1) + fun_l1_n921(x) + else + fun_l1_n271(x) + end +end + +def fun_l0_n938(x) + if (x < 1) + fun_l1_n222(x) + else + fun_l1_n170(x) + end +end + +def fun_l0_n939(x) + if (x < 1) + fun_l1_n567(x) + else + fun_l1_n463(x) + end +end + +def fun_l0_n940(x) + if (x < 1) + fun_l1_n456(x) + else + fun_l1_n192(x) + end +end + +def fun_l0_n941(x) + if (x < 1) + fun_l1_n32(x) + else + fun_l1_n951(x) + end +end + +def fun_l0_n942(x) + if (x < 1) + fun_l1_n14(x) + else + fun_l1_n135(x) + end +end + +def fun_l0_n943(x) + if (x < 1) + fun_l1_n15(x) + else + fun_l1_n234(x) + end +end + +def fun_l0_n944(x) + if (x < 1) + fun_l1_n119(x) + else + fun_l1_n33(x) + end +end + +def fun_l0_n945(x) + if (x < 1) + fun_l1_n859(x) + else + fun_l1_n497(x) + end +end + +def fun_l0_n946(x) + if (x < 1) + fun_l1_n178(x) + else + fun_l1_n369(x) + end +end + +def fun_l0_n947(x) + if (x < 1) + fun_l1_n64(x) + else + fun_l1_n356(x) + end +end + +def fun_l0_n948(x) + if (x < 1) + fun_l1_n279(x) + else + fun_l1_n244(x) + end +end + +def fun_l0_n949(x) + if (x < 1) + fun_l1_n728(x) + else + fun_l1_n548(x) + end +end + +def fun_l0_n950(x) + if (x < 1) + fun_l1_n3(x) + else + fun_l1_n698(x) + end +end + +def fun_l0_n951(x) + if (x < 1) + fun_l1_n665(x) + else + fun_l1_n198(x) + end +end + +def fun_l0_n952(x) + if (x < 1) + fun_l1_n812(x) + else + fun_l1_n964(x) + end +end + +def fun_l0_n953(x) + if (x < 1) + fun_l1_n851(x) + else + fun_l1_n877(x) + end +end + +def fun_l0_n954(x) + if (x < 1) + fun_l1_n415(x) + else + fun_l1_n379(x) + end +end + +def fun_l0_n955(x) + if (x < 1) + fun_l1_n806(x) + else + fun_l1_n919(x) + end +end + +def fun_l0_n956(x) + if (x < 1) + fun_l1_n207(x) + else + fun_l1_n798(x) + end +end + +def fun_l0_n957(x) + if (x < 1) + fun_l1_n269(x) + else + fun_l1_n416(x) + end +end + +def fun_l0_n958(x) + if (x < 1) + fun_l1_n654(x) + else + fun_l1_n236(x) + end +end + +def fun_l0_n959(x) + if (x < 1) + fun_l1_n392(x) + else + fun_l1_n623(x) + end +end + +def fun_l0_n960(x) + if (x < 1) + fun_l1_n889(x) + else + fun_l1_n279(x) + end +end + +def fun_l0_n961(x) + if (x < 1) + fun_l1_n476(x) + else + fun_l1_n201(x) + end +end + +def fun_l0_n962(x) + if (x < 1) + fun_l1_n799(x) + else + fun_l1_n570(x) + end +end + +def fun_l0_n963(x) + if (x < 1) + fun_l1_n802(x) + else + fun_l1_n114(x) + end +end + +def fun_l0_n964(x) + if (x < 1) + fun_l1_n197(x) + else + fun_l1_n422(x) + end +end + +def fun_l0_n965(x) + if (x < 1) + fun_l1_n37(x) + else + fun_l1_n691(x) + end +end + +def fun_l0_n966(x) + if (x < 1) + fun_l1_n775(x) + else + fun_l1_n854(x) + end +end + +def fun_l0_n967(x) + if (x < 1) + fun_l1_n373(x) + else + fun_l1_n306(x) + end +end + +def fun_l0_n968(x) + if (x < 1) + fun_l1_n154(x) + else + fun_l1_n122(x) + end +end + +def fun_l0_n969(x) + if (x < 1) + fun_l1_n497(x) + else + fun_l1_n456(x) + end +end + +def fun_l0_n970(x) + if (x < 1) + fun_l1_n621(x) + else + fun_l1_n931(x) + end +end + +def fun_l0_n971(x) + if (x < 1) + fun_l1_n963(x) + else + fun_l1_n613(x) + end +end + +def fun_l0_n972(x) + if (x < 1) + fun_l1_n613(x) + else + fun_l1_n508(x) + end +end + +def fun_l0_n973(x) + if (x < 1) + fun_l1_n466(x) + else + fun_l1_n929(x) + end +end + +def fun_l0_n974(x) + if (x < 1) + fun_l1_n247(x) + else + fun_l1_n610(x) + end +end + +def fun_l0_n975(x) + if (x < 1) + fun_l1_n674(x) + else + fun_l1_n609(x) + end +end + +def fun_l0_n976(x) + if (x < 1) + fun_l1_n963(x) + else + fun_l1_n601(x) + end +end + +def fun_l0_n977(x) + if (x < 1) + fun_l1_n728(x) + else + fun_l1_n242(x) + end +end + +def fun_l0_n978(x) + if (x < 1) + fun_l1_n515(x) + else + fun_l1_n113(x) + end +end + +def fun_l0_n979(x) + if (x < 1) + fun_l1_n734(x) + else + fun_l1_n271(x) + end +end + +def fun_l0_n980(x) + if (x < 1) + fun_l1_n837(x) + else + fun_l1_n733(x) + end +end + +def fun_l0_n981(x) + if (x < 1) + fun_l1_n326(x) + else + fun_l1_n213(x) + end +end + +def fun_l0_n982(x) + if (x < 1) + fun_l1_n733(x) + else + fun_l1_n198(x) + end +end + +def fun_l0_n983(x) + if (x < 1) + fun_l1_n989(x) + else + fun_l1_n700(x) + end +end + +def fun_l0_n984(x) + if (x < 1) + fun_l1_n266(x) + else + fun_l1_n348(x) + end +end + +def fun_l0_n985(x) + if (x < 1) + fun_l1_n931(x) + else + fun_l1_n646(x) + end +end + +def fun_l0_n986(x) + if (x < 1) + fun_l1_n435(x) + else + fun_l1_n747(x) + end +end + +def fun_l0_n987(x) + if (x < 1) + fun_l1_n474(x) + else + fun_l1_n638(x) + end +end + +def fun_l0_n988(x) + if (x < 1) + fun_l1_n615(x) + else + fun_l1_n283(x) + end +end + +def fun_l0_n989(x) + if (x < 1) + fun_l1_n410(x) + else + fun_l1_n289(x) + end +end + +def fun_l0_n990(x) + if (x < 1) + fun_l1_n633(x) + else + fun_l1_n77(x) + end +end + +def fun_l0_n991(x) + if (x < 1) + fun_l1_n153(x) + else + fun_l1_n255(x) + end +end + +def fun_l0_n992(x) + if (x < 1) + fun_l1_n447(x) + else + fun_l1_n239(x) + end +end + +def fun_l0_n993(x) + if (x < 1) + fun_l1_n837(x) + else + fun_l1_n582(x) + end +end + +def fun_l0_n994(x) + if (x < 1) + fun_l1_n385(x) + else + fun_l1_n939(x) + end +end + +def fun_l0_n995(x) + if (x < 1) + fun_l1_n236(x) + else + fun_l1_n497(x) + end +end + +def fun_l0_n996(x) + if (x < 1) + fun_l1_n63(x) + else + fun_l1_n721(x) + end +end + +def fun_l0_n997(x) + if (x < 1) + fun_l1_n457(x) + else + fun_l1_n376(x) + end +end + +def fun_l0_n998(x) + if (x < 1) + fun_l1_n362(x) + else + fun_l1_n478(x) + end +end + +def fun_l0_n999(x) + if (x < 1) + fun_l1_n554(x) + else + fun_l1_n422(x) + end +end + +def fun_l1_n0(x) + if (x < 1) + fun_l2_n498(x) + else + fun_l2_n461(x) + end +end + +def fun_l1_n1(x) + if (x < 1) + fun_l2_n424(x) + else + fun_l2_n137(x) + end +end + +def fun_l1_n2(x) + if (x < 1) + fun_l2_n325(x) + else + fun_l2_n95(x) + end +end + +def fun_l1_n3(x) + if (x < 1) + fun_l2_n917(x) + else + fun_l2_n469(x) + end +end + +def fun_l1_n4(x) + if (x < 1) + fun_l2_n730(x) + else + fun_l2_n147(x) + end +end + +def fun_l1_n5(x) + if (x < 1) + fun_l2_n796(x) + else + fun_l2_n907(x) + end +end + +def fun_l1_n6(x) + if (x < 1) + fun_l2_n555(x) + else + fun_l2_n309(x) + end +end + +def fun_l1_n7(x) + if (x < 1) + fun_l2_n475(x) + else + fun_l2_n193(x) + end +end + +def fun_l1_n8(x) + if (x < 1) + fun_l2_n317(x) + else + fun_l2_n284(x) + end +end + +def fun_l1_n9(x) + if (x < 1) + fun_l2_n297(x) + else + fun_l2_n370(x) + end +end + +def fun_l1_n10(x) + if (x < 1) + fun_l2_n66(x) + else + fun_l2_n959(x) + end +end + +def fun_l1_n11(x) + if (x < 1) + fun_l2_n833(x) + else + fun_l2_n250(x) + end +end + +def fun_l1_n12(x) + if (x < 1) + fun_l2_n640(x) + else + fun_l2_n588(x) + end +end + +def fun_l1_n13(x) + if (x < 1) + fun_l2_n91(x) + else + fun_l2_n294(x) + end +end + +def fun_l1_n14(x) + if (x < 1) + fun_l2_n166(x) + else + fun_l2_n943(x) + end +end + +def fun_l1_n15(x) + if (x < 1) + fun_l2_n352(x) + else + fun_l2_n363(x) + end +end + +def fun_l1_n16(x) + if (x < 1) + fun_l2_n84(x) + else + fun_l2_n278(x) + end +end + +def fun_l1_n17(x) + if (x < 1) + fun_l2_n308(x) + else + fun_l2_n341(x) + end +end + +def fun_l1_n18(x) + if (x < 1) + fun_l2_n713(x) + else + fun_l2_n575(x) + end +end + +def fun_l1_n19(x) + if (x < 1) + fun_l2_n81(x) + else + fun_l2_n301(x) + end +end + +def fun_l1_n20(x) + if (x < 1) + fun_l2_n516(x) + else + fun_l2_n164(x) + end +end + +def fun_l1_n21(x) + if (x < 1) + fun_l2_n900(x) + else + fun_l2_n963(x) + end +end + +def fun_l1_n22(x) + if (x < 1) + fun_l2_n472(x) + else + fun_l2_n288(x) + end +end + +def fun_l1_n23(x) + if (x < 1) + fun_l2_n587(x) + else + fun_l2_n515(x) + end +end + +def fun_l1_n24(x) + if (x < 1) + fun_l2_n506(x) + else + fun_l2_n711(x) + end +end + +def fun_l1_n25(x) + if (x < 1) + fun_l2_n935(x) + else + fun_l2_n177(x) + end +end + +def fun_l1_n26(x) + if (x < 1) + fun_l2_n485(x) + else + fun_l2_n368(x) + end +end + +def fun_l1_n27(x) + if (x < 1) + fun_l2_n984(x) + else + fun_l2_n290(x) + end +end + +def fun_l1_n28(x) + if (x < 1) + fun_l2_n36(x) + else + fun_l2_n837(x) + end +end + +def fun_l1_n29(x) + if (x < 1) + fun_l2_n752(x) + else + fun_l2_n311(x) + end +end + +def fun_l1_n30(x) + if (x < 1) + fun_l2_n215(x) + else + fun_l2_n376(x) + end +end + +def fun_l1_n31(x) + if (x < 1) + fun_l2_n533(x) + else + fun_l2_n92(x) + end +end + +def fun_l1_n32(x) + if (x < 1) + fun_l2_n386(x) + else + fun_l2_n283(x) + end +end + +def fun_l1_n33(x) + if (x < 1) + fun_l2_n268(x) + else + fun_l2_n256(x) + end +end + +def fun_l1_n34(x) + if (x < 1) + fun_l2_n348(x) + else + fun_l2_n464(x) + end +end + +def fun_l1_n35(x) + if (x < 1) + fun_l2_n25(x) + else + fun_l2_n33(x) + end +end + +def fun_l1_n36(x) + if (x < 1) + fun_l2_n387(x) + else + fun_l2_n341(x) + end +end + +def fun_l1_n37(x) + if (x < 1) + fun_l2_n580(x) + else + fun_l2_n476(x) + end +end + +def fun_l1_n38(x) + if (x < 1) + fun_l2_n33(x) + else + fun_l2_n33(x) + end +end + +def fun_l1_n39(x) + if (x < 1) + fun_l2_n861(x) + else + fun_l2_n52(x) + end +end + +def fun_l1_n40(x) + if (x < 1) + fun_l2_n740(x) + else + fun_l2_n943(x) + end +end + +def fun_l1_n41(x) + if (x < 1) + fun_l2_n956(x) + else + fun_l2_n159(x) + end +end + +def fun_l1_n42(x) + if (x < 1) + fun_l2_n445(x) + else + fun_l2_n775(x) + end +end + +def fun_l1_n43(x) + if (x < 1) + fun_l2_n667(x) + else + fun_l2_n282(x) + end +end + +def fun_l1_n44(x) + if (x < 1) + fun_l2_n185(x) + else + fun_l2_n210(x) + end +end + +def fun_l1_n45(x) + if (x < 1) + fun_l2_n490(x) + else + fun_l2_n21(x) + end +end + +def fun_l1_n46(x) + if (x < 1) + fun_l2_n751(x) + else + fun_l2_n639(x) + end +end + +def fun_l1_n47(x) + if (x < 1) + fun_l2_n431(x) + else + fun_l2_n917(x) + end +end + +def fun_l1_n48(x) + if (x < 1) + fun_l2_n187(x) + else + fun_l2_n170(x) + end +end + +def fun_l1_n49(x) + if (x < 1) + fun_l2_n780(x) + else + fun_l2_n77(x) + end +end + +def fun_l1_n50(x) + if (x < 1) + fun_l2_n546(x) + else + fun_l2_n759(x) + end +end + +def fun_l1_n51(x) + if (x < 1) + fun_l2_n453(x) + else + fun_l2_n951(x) + end +end + +def fun_l1_n52(x) + if (x < 1) + fun_l2_n989(x) + else + fun_l2_n716(x) + end +end + +def fun_l1_n53(x) + if (x < 1) + fun_l2_n652(x) + else + fun_l2_n36(x) + end +end + +def fun_l1_n54(x) + if (x < 1) + fun_l2_n187(x) + else + fun_l2_n760(x) + end +end + +def fun_l1_n55(x) + if (x < 1) + fun_l2_n706(x) + else + fun_l2_n927(x) + end +end + +def fun_l1_n56(x) + if (x < 1) + fun_l2_n666(x) + else + fun_l2_n295(x) + end +end + +def fun_l1_n57(x) + if (x < 1) + fun_l2_n424(x) + else + fun_l2_n447(x) + end +end + +def fun_l1_n58(x) + if (x < 1) + fun_l2_n408(x) + else + fun_l2_n337(x) + end +end + +def fun_l1_n59(x) + if (x < 1) + fun_l2_n567(x) + else + fun_l2_n765(x) + end +end + +def fun_l1_n60(x) + if (x < 1) + fun_l2_n241(x) + else + fun_l2_n701(x) + end +end + +def fun_l1_n61(x) + if (x < 1) + fun_l2_n831(x) + else + fun_l2_n382(x) + end +end + +def fun_l1_n62(x) + if (x < 1) + fun_l2_n634(x) + else + fun_l2_n908(x) + end +end + +def fun_l1_n63(x) + if (x < 1) + fun_l2_n696(x) + else + fun_l2_n48(x) + end +end + +def fun_l1_n64(x) + if (x < 1) + fun_l2_n528(x) + else + fun_l2_n110(x) + end +end + +def fun_l1_n65(x) + if (x < 1) + fun_l2_n767(x) + else + fun_l2_n973(x) + end +end + +def fun_l1_n66(x) + if (x < 1) + fun_l2_n963(x) + else + fun_l2_n673(x) + end +end + +def fun_l1_n67(x) + if (x < 1) + fun_l2_n927(x) + else + fun_l2_n309(x) + end +end + +def fun_l1_n68(x) + if (x < 1) + fun_l2_n851(x) + else + fun_l2_n160(x) + end +end + +def fun_l1_n69(x) + if (x < 1) + fun_l2_n575(x) + else + fun_l2_n800(x) + end +end + +def fun_l1_n70(x) + if (x < 1) + fun_l2_n396(x) + else + fun_l2_n382(x) + end +end + +def fun_l1_n71(x) + if (x < 1) + fun_l2_n508(x) + else + fun_l2_n763(x) + end +end + +def fun_l1_n72(x) + if (x < 1) + fun_l2_n360(x) + else + fun_l2_n848(x) + end +end + +def fun_l1_n73(x) + if (x < 1) + fun_l2_n878(x) + else + fun_l2_n83(x) + end +end + +def fun_l1_n74(x) + if (x < 1) + fun_l2_n573(x) + else + fun_l2_n284(x) + end +end + +def fun_l1_n75(x) + if (x < 1) + fun_l2_n999(x) + else + fun_l2_n954(x) + end +end + +def fun_l1_n76(x) + if (x < 1) + fun_l2_n182(x) + else + fun_l2_n997(x) + end +end + +def fun_l1_n77(x) + if (x < 1) + fun_l2_n802(x) + else + fun_l2_n409(x) + end +end + +def fun_l1_n78(x) + if (x < 1) + fun_l2_n163(x) + else + fun_l2_n417(x) + end +end + +def fun_l1_n79(x) + if (x < 1) + fun_l2_n328(x) + else + fun_l2_n688(x) + end +end + +def fun_l1_n80(x) + if (x < 1) + fun_l2_n699(x) + else + fun_l2_n356(x) + end +end + +def fun_l1_n81(x) + if (x < 1) + fun_l2_n44(x) + else + fun_l2_n302(x) + end +end + +def fun_l1_n82(x) + if (x < 1) + fun_l2_n597(x) + else + fun_l2_n892(x) + end +end + +def fun_l1_n83(x) + if (x < 1) + fun_l2_n494(x) + else + fun_l2_n547(x) + end +end + +def fun_l1_n84(x) + if (x < 1) + fun_l2_n631(x) + else + fun_l2_n922(x) + end +end + +def fun_l1_n85(x) + if (x < 1) + fun_l2_n797(x) + else + fun_l2_n874(x) + end +end + +def fun_l1_n86(x) + if (x < 1) + fun_l2_n209(x) + else + fun_l2_n365(x) + end +end + +def fun_l1_n87(x) + if (x < 1) + fun_l2_n516(x) + else + fun_l2_n371(x) + end +end + +def fun_l1_n88(x) + if (x < 1) + fun_l2_n654(x) + else + fun_l2_n88(x) + end +end + +def fun_l1_n89(x) + if (x < 1) + fun_l2_n70(x) + else + fun_l2_n521(x) + end +end + +def fun_l1_n90(x) + if (x < 1) + fun_l2_n666(x) + else + fun_l2_n749(x) + end +end + +def fun_l1_n91(x) + if (x < 1) + fun_l2_n504(x) + else + fun_l2_n339(x) + end +end + +def fun_l1_n92(x) + if (x < 1) + fun_l2_n734(x) + else + fun_l2_n277(x) + end +end + +def fun_l1_n93(x) + if (x < 1) + fun_l2_n300(x) + else + fun_l2_n826(x) + end +end + +def fun_l1_n94(x) + if (x < 1) + fun_l2_n733(x) + else + fun_l2_n560(x) + end +end + +def fun_l1_n95(x) + if (x < 1) + fun_l2_n834(x) + else + fun_l2_n644(x) + end +end + +def fun_l1_n96(x) + if (x < 1) + fun_l2_n183(x) + else + fun_l2_n238(x) + end +end + +def fun_l1_n97(x) + if (x < 1) + fun_l2_n372(x) + else + fun_l2_n753(x) + end +end + +def fun_l1_n98(x) + if (x < 1) + fun_l2_n208(x) + else + fun_l2_n543(x) + end +end + +def fun_l1_n99(x) + if (x < 1) + fun_l2_n38(x) + else + fun_l2_n974(x) + end +end + +def fun_l1_n100(x) + if (x < 1) + fun_l2_n620(x) + else + fun_l2_n503(x) + end +end + +def fun_l1_n101(x) + if (x < 1) + fun_l2_n248(x) + else + fun_l2_n189(x) + end +end + +def fun_l1_n102(x) + if (x < 1) + fun_l2_n489(x) + else + fun_l2_n390(x) + end +end + +def fun_l1_n103(x) + if (x < 1) + fun_l2_n452(x) + else + fun_l2_n22(x) + end +end + +def fun_l1_n104(x) + if (x < 1) + fun_l2_n542(x) + else + fun_l2_n135(x) + end +end + +def fun_l1_n105(x) + if (x < 1) + fun_l2_n988(x) + else + fun_l2_n118(x) + end +end + +def fun_l1_n106(x) + if (x < 1) + fun_l2_n817(x) + else + fun_l2_n534(x) + end +end + +def fun_l1_n107(x) + if (x < 1) + fun_l2_n812(x) + else + fun_l2_n206(x) + end +end + +def fun_l1_n108(x) + if (x < 1) + fun_l2_n514(x) + else + fun_l2_n300(x) + end +end + +def fun_l1_n109(x) + if (x < 1) + fun_l2_n482(x) + else + fun_l2_n376(x) + end +end + +def fun_l1_n110(x) + if (x < 1) + fun_l2_n571(x) + else + fun_l2_n758(x) + end +end + +def fun_l1_n111(x) + if (x < 1) + fun_l2_n971(x) + else + fun_l2_n811(x) + end +end + +def fun_l1_n112(x) + if (x < 1) + fun_l2_n23(x) + else + fun_l2_n844(x) + end +end + +def fun_l1_n113(x) + if (x < 1) + fun_l2_n865(x) + else + fun_l2_n149(x) + end +end + +def fun_l1_n114(x) + if (x < 1) + fun_l2_n930(x) + else + fun_l2_n327(x) + end +end + +def fun_l1_n115(x) + if (x < 1) + fun_l2_n67(x) + else + fun_l2_n442(x) + end +end + +def fun_l1_n116(x) + if (x < 1) + fun_l2_n278(x) + else + fun_l2_n653(x) + end +end + +def fun_l1_n117(x) + if (x < 1) + fun_l2_n457(x) + else + fun_l2_n179(x) + end +end + +def fun_l1_n118(x) + if (x < 1) + fun_l2_n384(x) + else + fun_l2_n594(x) + end +end + +def fun_l1_n119(x) + if (x < 1) + fun_l2_n361(x) + else + fun_l2_n770(x) + end +end + +def fun_l1_n120(x) + if (x < 1) + fun_l2_n24(x) + else + fun_l2_n269(x) + end +end + +def fun_l1_n121(x) + if (x < 1) + fun_l2_n57(x) + else + fun_l2_n2(x) + end +end + +def fun_l1_n122(x) + if (x < 1) + fun_l2_n421(x) + else + fun_l2_n339(x) + end +end + +def fun_l1_n123(x) + if (x < 1) + fun_l2_n623(x) + else + fun_l2_n985(x) + end +end + +def fun_l1_n124(x) + if (x < 1) + fun_l2_n609(x) + else + fun_l2_n530(x) + end +end + +def fun_l1_n125(x) + if (x < 1) + fun_l2_n565(x) + else + fun_l2_n155(x) + end +end + +def fun_l1_n126(x) + if (x < 1) + fun_l2_n135(x) + else + fun_l2_n971(x) + end +end + +def fun_l1_n127(x) + if (x < 1) + fun_l2_n691(x) + else + fun_l2_n503(x) + end +end + +def fun_l1_n128(x) + if (x < 1) + fun_l2_n555(x) + else + fun_l2_n67(x) + end +end + +def fun_l1_n129(x) + if (x < 1) + fun_l2_n95(x) + else + fun_l2_n192(x) + end +end + +def fun_l1_n130(x) + if (x < 1) + fun_l2_n432(x) + else + fun_l2_n771(x) + end +end + +def fun_l1_n131(x) + if (x < 1) + fun_l2_n993(x) + else + fun_l2_n539(x) + end +end + +def fun_l1_n132(x) + if (x < 1) + fun_l2_n809(x) + else + fun_l2_n902(x) + end +end + +def fun_l1_n133(x) + if (x < 1) + fun_l2_n95(x) + else + fun_l2_n928(x) + end +end + +def fun_l1_n134(x) + if (x < 1) + fun_l2_n781(x) + else + fun_l2_n832(x) + end +end + +def fun_l1_n135(x) + if (x < 1) + fun_l2_n231(x) + else + fun_l2_n392(x) + end +end + +def fun_l1_n136(x) + if (x < 1) + fun_l2_n102(x) + else + fun_l2_n449(x) + end +end + +def fun_l1_n137(x) + if (x < 1) + fun_l2_n196(x) + else + fun_l2_n909(x) + end +end + +def fun_l1_n138(x) + if (x < 1) + fun_l2_n449(x) + else + fun_l2_n190(x) + end +end + +def fun_l1_n139(x) + if (x < 1) + fun_l2_n638(x) + else + fun_l2_n149(x) + end +end + +def fun_l1_n140(x) + if (x < 1) + fun_l2_n523(x) + else + fun_l2_n297(x) + end +end + +def fun_l1_n141(x) + if (x < 1) + fun_l2_n476(x) + else + fun_l2_n161(x) + end +end + +def fun_l1_n142(x) + if (x < 1) + fun_l2_n893(x) + else + fun_l2_n686(x) + end +end + +def fun_l1_n143(x) + if (x < 1) + fun_l2_n958(x) + else + fun_l2_n899(x) + end +end + +def fun_l1_n144(x) + if (x < 1) + fun_l2_n104(x) + else + fun_l2_n912(x) + end +end + +def fun_l1_n145(x) + if (x < 1) + fun_l2_n332(x) + else + fun_l2_n579(x) + end +end + +def fun_l1_n146(x) + if (x < 1) + fun_l2_n40(x) + else + fun_l2_n468(x) + end +end + +def fun_l1_n147(x) + if (x < 1) + fun_l2_n771(x) + else + fun_l2_n314(x) + end +end + +def fun_l1_n148(x) + if (x < 1) + fun_l2_n340(x) + else + fun_l2_n203(x) + end +end + +def fun_l1_n149(x) + if (x < 1) + fun_l2_n952(x) + else + fun_l2_n265(x) + end +end + +def fun_l1_n150(x) + if (x < 1) + fun_l2_n11(x) + else + fun_l2_n303(x) + end +end + +def fun_l1_n151(x) + if (x < 1) + fun_l2_n394(x) + else + fun_l2_n317(x) + end +end + +def fun_l1_n152(x) + if (x < 1) + fun_l2_n140(x) + else + fun_l2_n906(x) + end +end + +def fun_l1_n153(x) + if (x < 1) + fun_l2_n75(x) + else + fun_l2_n485(x) + end +end + +def fun_l1_n154(x) + if (x < 1) + fun_l2_n842(x) + else + fun_l2_n370(x) + end +end + +def fun_l1_n155(x) + if (x < 1) + fun_l2_n346(x) + else + fun_l2_n833(x) + end +end + +def fun_l1_n156(x) + if (x < 1) + fun_l2_n433(x) + else + fun_l2_n373(x) + end +end + +def fun_l1_n157(x) + if (x < 1) + fun_l2_n978(x) + else + fun_l2_n99(x) + end +end + +def fun_l1_n158(x) + if (x < 1) + fun_l2_n540(x) + else + fun_l2_n987(x) + end +end + +def fun_l1_n159(x) + if (x < 1) + fun_l2_n785(x) + else + fun_l2_n478(x) + end +end + +def fun_l1_n160(x) + if (x < 1) + fun_l2_n282(x) + else + fun_l2_n222(x) + end +end + +def fun_l1_n161(x) + if (x < 1) + fun_l2_n29(x) + else + fun_l2_n946(x) + end +end + +def fun_l1_n162(x) + if (x < 1) + fun_l2_n314(x) + else + fun_l2_n758(x) + end +end + +def fun_l1_n163(x) + if (x < 1) + fun_l2_n861(x) + else + fun_l2_n799(x) + end +end + +def fun_l1_n164(x) + if (x < 1) + fun_l2_n403(x) + else + fun_l2_n413(x) + end +end + +def fun_l1_n165(x) + if (x < 1) + fun_l2_n160(x) + else + fun_l2_n794(x) + end +end + +def fun_l1_n166(x) + if (x < 1) + fun_l2_n114(x) + else + fun_l2_n238(x) + end +end + +def fun_l1_n167(x) + if (x < 1) + fun_l2_n806(x) + else + fun_l2_n233(x) + end +end + +def fun_l1_n168(x) + if (x < 1) + fun_l2_n871(x) + else + fun_l2_n53(x) + end +end + +def fun_l1_n169(x) + if (x < 1) + fun_l2_n644(x) + else + fun_l2_n503(x) + end +end + +def fun_l1_n170(x) + if (x < 1) + fun_l2_n172(x) + else + fun_l2_n289(x) + end +end + +def fun_l1_n171(x) + if (x < 1) + fun_l2_n920(x) + else + fun_l2_n851(x) + end +end + +def fun_l1_n172(x) + if (x < 1) + fun_l2_n552(x) + else + fun_l2_n822(x) + end +end + +def fun_l1_n173(x) + if (x < 1) + fun_l2_n747(x) + else + fun_l2_n265(x) + end +end + +def fun_l1_n174(x) + if (x < 1) + fun_l2_n287(x) + else + fun_l2_n762(x) + end +end + +def fun_l1_n175(x) + if (x < 1) + fun_l2_n627(x) + else + fun_l2_n951(x) + end +end + +def fun_l1_n176(x) + if (x < 1) + fun_l2_n459(x) + else + fun_l2_n962(x) + end +end + +def fun_l1_n177(x) + if (x < 1) + fun_l2_n214(x) + else + fun_l2_n145(x) + end +end + +def fun_l1_n178(x) + if (x < 1) + fun_l2_n620(x) + else + fun_l2_n995(x) + end +end + +def fun_l1_n179(x) + if (x < 1) + fun_l2_n136(x) + else + fun_l2_n283(x) + end +end + +def fun_l1_n180(x) + if (x < 1) + fun_l2_n377(x) + else + fun_l2_n672(x) + end +end + +def fun_l1_n181(x) + if (x < 1) + fun_l2_n218(x) + else + fun_l2_n434(x) + end +end + +def fun_l1_n182(x) + if (x < 1) + fun_l2_n522(x) + else + fun_l2_n525(x) + end +end + +def fun_l1_n183(x) + if (x < 1) + fun_l2_n648(x) + else + fun_l2_n725(x) + end +end + +def fun_l1_n184(x) + if (x < 1) + fun_l2_n579(x) + else + fun_l2_n974(x) + end +end + +def fun_l1_n185(x) + if (x < 1) + fun_l2_n10(x) + else + fun_l2_n514(x) + end +end + +def fun_l1_n186(x) + if (x < 1) + fun_l2_n829(x) + else + fun_l2_n754(x) + end +end + +def fun_l1_n187(x) + if (x < 1) + fun_l2_n380(x) + else + fun_l2_n401(x) + end +end + +def fun_l1_n188(x) + if (x < 1) + fun_l2_n839(x) + else + fun_l2_n453(x) + end +end + +def fun_l1_n189(x) + if (x < 1) + fun_l2_n553(x) + else + fun_l2_n553(x) + end +end + +def fun_l1_n190(x) + if (x < 1) + fun_l2_n972(x) + else + fun_l2_n187(x) + end +end + +def fun_l1_n191(x) + if (x < 1) + fun_l2_n275(x) + else + fun_l2_n11(x) + end +end + +def fun_l1_n192(x) + if (x < 1) + fun_l2_n1(x) + else + fun_l2_n927(x) + end +end + +def fun_l1_n193(x) + if (x < 1) + fun_l2_n569(x) + else + fun_l2_n557(x) + end +end + +def fun_l1_n194(x) + if (x < 1) + fun_l2_n380(x) + else + fun_l2_n187(x) + end +end + +def fun_l1_n195(x) + if (x < 1) + fun_l2_n297(x) + else + fun_l2_n813(x) + end +end + +def fun_l1_n196(x) + if (x < 1) + fun_l2_n565(x) + else + fun_l2_n514(x) + end +end + +def fun_l1_n197(x) + if (x < 1) + fun_l2_n473(x) + else + fun_l2_n64(x) + end +end + +def fun_l1_n198(x) + if (x < 1) + fun_l2_n182(x) + else + fun_l2_n363(x) + end +end + +def fun_l1_n199(x) + if (x < 1) + fun_l2_n660(x) + else + fun_l2_n506(x) + end +end + +def fun_l1_n200(x) + if (x < 1) + fun_l2_n834(x) + else + fun_l2_n348(x) + end +end + +def fun_l1_n201(x) + if (x < 1) + fun_l2_n403(x) + else + fun_l2_n432(x) + end +end + +def fun_l1_n202(x) + if (x < 1) + fun_l2_n115(x) + else + fun_l2_n145(x) + end +end + +def fun_l1_n203(x) + if (x < 1) + fun_l2_n637(x) + else + fun_l2_n4(x) + end +end + +def fun_l1_n204(x) + if (x < 1) + fun_l2_n951(x) + else + fun_l2_n753(x) + end +end + +def fun_l1_n205(x) + if (x < 1) + fun_l2_n703(x) + else + fun_l2_n563(x) + end +end + +def fun_l1_n206(x) + if (x < 1) + fun_l2_n614(x) + else + fun_l2_n563(x) + end +end + +def fun_l1_n207(x) + if (x < 1) + fun_l2_n210(x) + else + fun_l2_n139(x) + end +end + +def fun_l1_n208(x) + if (x < 1) + fun_l2_n471(x) + else + fun_l2_n346(x) + end +end + +def fun_l1_n209(x) + if (x < 1) + fun_l2_n922(x) + else + fun_l2_n54(x) + end +end + +def fun_l1_n210(x) + if (x < 1) + fun_l2_n494(x) + else + fun_l2_n41(x) + end +end + +def fun_l1_n211(x) + if (x < 1) + fun_l2_n87(x) + else + fun_l2_n190(x) + end +end + +def fun_l1_n212(x) + if (x < 1) + fun_l2_n458(x) + else + fun_l2_n885(x) + end +end + +def fun_l1_n213(x) + if (x < 1) + fun_l2_n48(x) + else + fun_l2_n225(x) + end +end + +def fun_l1_n214(x) + if (x < 1) + fun_l2_n706(x) + else + fun_l2_n694(x) + end +end + +def fun_l1_n215(x) + if (x < 1) + fun_l2_n116(x) + else + fun_l2_n233(x) + end +end + +def fun_l1_n216(x) + if (x < 1) + fun_l2_n279(x) + else + fun_l2_n857(x) + end +end + +def fun_l1_n217(x) + if (x < 1) + fun_l2_n643(x) + else + fun_l2_n289(x) + end +end + +def fun_l1_n218(x) + if (x < 1) + fun_l2_n535(x) + else + fun_l2_n833(x) + end +end + +def fun_l1_n219(x) + if (x < 1) + fun_l2_n478(x) + else + fun_l2_n793(x) + end +end + +def fun_l1_n220(x) + if (x < 1) + fun_l2_n57(x) + else + fun_l2_n64(x) + end +end + +def fun_l1_n221(x) + if (x < 1) + fun_l2_n36(x) + else + fun_l2_n74(x) + end +end + +def fun_l1_n222(x) + if (x < 1) + fun_l2_n438(x) + else + fun_l2_n616(x) + end +end + +def fun_l1_n223(x) + if (x < 1) + fun_l2_n900(x) + else + fun_l2_n744(x) + end +end + +def fun_l1_n224(x) + if (x < 1) + fun_l2_n780(x) + else + fun_l2_n213(x) + end +end + +def fun_l1_n225(x) + if (x < 1) + fun_l2_n916(x) + else + fun_l2_n471(x) + end +end + +def fun_l1_n226(x) + if (x < 1) + fun_l2_n236(x) + else + fun_l2_n980(x) + end +end + +def fun_l1_n227(x) + if (x < 1) + fun_l2_n28(x) + else + fun_l2_n291(x) + end +end + +def fun_l1_n228(x) + if (x < 1) + fun_l2_n35(x) + else + fun_l2_n353(x) + end +end + +def fun_l1_n229(x) + if (x < 1) + fun_l2_n404(x) + else + fun_l2_n254(x) + end +end + +def fun_l1_n230(x) + if (x < 1) + fun_l2_n641(x) + else + fun_l2_n464(x) + end +end + +def fun_l1_n231(x) + if (x < 1) + fun_l2_n592(x) + else + fun_l2_n646(x) + end +end + +def fun_l1_n232(x) + if (x < 1) + fun_l2_n162(x) + else + fun_l2_n215(x) + end +end + +def fun_l1_n233(x) + if (x < 1) + fun_l2_n219(x) + else + fun_l2_n772(x) + end +end + +def fun_l1_n234(x) + if (x < 1) + fun_l2_n304(x) + else + fun_l2_n963(x) + end +end + +def fun_l1_n235(x) + if (x < 1) + fun_l2_n610(x) + else + fun_l2_n716(x) + end +end + +def fun_l1_n236(x) + if (x < 1) + fun_l2_n151(x) + else + fun_l2_n498(x) + end +end + +def fun_l1_n237(x) + if (x < 1) + fun_l2_n288(x) + else + fun_l2_n261(x) + end +end + +def fun_l1_n238(x) + if (x < 1) + fun_l2_n139(x) + else + fun_l2_n18(x) + end +end + +def fun_l1_n239(x) + if (x < 1) + fun_l2_n559(x) + else + fun_l2_n148(x) + end +end + +def fun_l1_n240(x) + if (x < 1) + fun_l2_n520(x) + else + fun_l2_n250(x) + end +end + +def fun_l1_n241(x) + if (x < 1) + fun_l2_n42(x) + else + fun_l2_n196(x) + end +end + +def fun_l1_n242(x) + if (x < 1) + fun_l2_n16(x) + else + fun_l2_n540(x) + end +end + +def fun_l1_n243(x) + if (x < 1) + fun_l2_n539(x) + else + fun_l2_n890(x) + end +end + +def fun_l1_n244(x) + if (x < 1) + fun_l2_n559(x) + else + fun_l2_n423(x) + end +end + +def fun_l1_n245(x) + if (x < 1) + fun_l2_n110(x) + else + fun_l2_n711(x) + end +end + +def fun_l1_n246(x) + if (x < 1) + fun_l2_n151(x) + else + fun_l2_n407(x) + end +end + +def fun_l1_n247(x) + if (x < 1) + fun_l2_n118(x) + else + fun_l2_n209(x) + end +end + +def fun_l1_n248(x) + if (x < 1) + fun_l2_n764(x) + else + fun_l2_n932(x) + end +end + +def fun_l1_n249(x) + if (x < 1) + fun_l2_n991(x) + else + fun_l2_n828(x) + end +end + +def fun_l1_n250(x) + if (x < 1) + fun_l2_n354(x) + else + fun_l2_n53(x) + end +end + +def fun_l1_n251(x) + if (x < 1) + fun_l2_n63(x) + else + fun_l2_n292(x) + end +end + +def fun_l1_n252(x) + if (x < 1) + fun_l2_n874(x) + else + fun_l2_n544(x) + end +end + +def fun_l1_n253(x) + if (x < 1) + fun_l2_n336(x) + else + fun_l2_n57(x) + end +end + +def fun_l1_n254(x) + if (x < 1) + fun_l2_n302(x) + else + fun_l2_n90(x) + end +end + +def fun_l1_n255(x) + if (x < 1) + fun_l2_n122(x) + else + fun_l2_n888(x) + end +end + +def fun_l1_n256(x) + if (x < 1) + fun_l2_n326(x) + else + fun_l2_n616(x) + end +end + +def fun_l1_n257(x) + if (x < 1) + fun_l2_n77(x) + else + fun_l2_n622(x) + end +end + +def fun_l1_n258(x) + if (x < 1) + fun_l2_n785(x) + else + fun_l2_n229(x) + end +end + +def fun_l1_n259(x) + if (x < 1) + fun_l2_n504(x) + else + fun_l2_n766(x) + end +end + +def fun_l1_n260(x) + if (x < 1) + fun_l2_n857(x) + else + fun_l2_n823(x) + end +end + +def fun_l1_n261(x) + if (x < 1) + fun_l2_n72(x) + else + fun_l2_n875(x) + end +end + +def fun_l1_n262(x) + if (x < 1) + fun_l2_n215(x) + else + fun_l2_n549(x) + end +end + +def fun_l1_n263(x) + if (x < 1) + fun_l2_n232(x) + else + fun_l2_n96(x) + end +end + +def fun_l1_n264(x) + if (x < 1) + fun_l2_n183(x) + else + fun_l2_n351(x) + end +end + +def fun_l1_n265(x) + if (x < 1) + fun_l2_n906(x) + else + fun_l2_n154(x) + end +end + +def fun_l1_n266(x) + if (x < 1) + fun_l2_n234(x) + else + fun_l2_n547(x) + end +end + +def fun_l1_n267(x) + if (x < 1) + fun_l2_n882(x) + else + fun_l2_n802(x) + end +end + +def fun_l1_n268(x) + if (x < 1) + fun_l2_n844(x) + else + fun_l2_n430(x) + end +end + +def fun_l1_n269(x) + if (x < 1) + fun_l2_n781(x) + else + fun_l2_n738(x) + end +end + +def fun_l1_n270(x) + if (x < 1) + fun_l2_n88(x) + else + fun_l2_n439(x) + end +end + +def fun_l1_n271(x) + if (x < 1) + fun_l2_n334(x) + else + fun_l2_n508(x) + end +end + +def fun_l1_n272(x) + if (x < 1) + fun_l2_n871(x) + else + fun_l2_n562(x) + end +end + +def fun_l1_n273(x) + if (x < 1) + fun_l2_n328(x) + else + fun_l2_n356(x) + end +end + +def fun_l1_n274(x) + if (x < 1) + fun_l2_n654(x) + else + fun_l2_n511(x) + end +end + +def fun_l1_n275(x) + if (x < 1) + fun_l2_n309(x) + else + fun_l2_n840(x) + end +end + +def fun_l1_n276(x) + if (x < 1) + fun_l2_n729(x) + else + fun_l2_n72(x) + end +end + +def fun_l1_n277(x) + if (x < 1) + fun_l2_n993(x) + else + fun_l2_n858(x) + end +end + +def fun_l1_n278(x) + if (x < 1) + fun_l2_n664(x) + else + fun_l2_n50(x) + end +end + +def fun_l1_n279(x) + if (x < 1) + fun_l2_n225(x) + else + fun_l2_n403(x) + end +end + +def fun_l1_n280(x) + if (x < 1) + fun_l2_n780(x) + else + fun_l2_n396(x) + end +end + +def fun_l1_n281(x) + if (x < 1) + fun_l2_n114(x) + else + fun_l2_n114(x) + end +end + +def fun_l1_n282(x) + if (x < 1) + fun_l2_n568(x) + else + fun_l2_n610(x) + end +end + +def fun_l1_n283(x) + if (x < 1) + fun_l2_n539(x) + else + fun_l2_n990(x) + end +end + +def fun_l1_n284(x) + if (x < 1) + fun_l2_n773(x) + else + fun_l2_n622(x) + end +end + +def fun_l1_n285(x) + if (x < 1) + fun_l2_n695(x) + else + fun_l2_n101(x) + end +end + +def fun_l1_n286(x) + if (x < 1) + fun_l2_n239(x) + else + fun_l2_n242(x) + end +end + +def fun_l1_n287(x) + if (x < 1) + fun_l2_n191(x) + else + fun_l2_n839(x) + end +end + +def fun_l1_n288(x) + if (x < 1) + fun_l2_n246(x) + else + fun_l2_n179(x) + end +end + +def fun_l1_n289(x) + if (x < 1) + fun_l2_n300(x) + else + fun_l2_n975(x) + end +end + +def fun_l1_n290(x) + if (x < 1) + fun_l2_n436(x) + else + fun_l2_n14(x) + end +end + +def fun_l1_n291(x) + if (x < 1) + fun_l2_n33(x) + else + fun_l2_n303(x) + end +end + +def fun_l1_n292(x) + if (x < 1) + fun_l2_n462(x) + else + fun_l2_n399(x) + end +end + +def fun_l1_n293(x) + if (x < 1) + fun_l2_n782(x) + else + fun_l2_n521(x) + end +end + +def fun_l1_n294(x) + if (x < 1) + fun_l2_n995(x) + else + fun_l2_n944(x) + end +end + +def fun_l1_n295(x) + if (x < 1) + fun_l2_n700(x) + else + fun_l2_n250(x) + end +end + +def fun_l1_n296(x) + if (x < 1) + fun_l2_n866(x) + else + fun_l2_n566(x) + end +end + +def fun_l1_n297(x) + if (x < 1) + fun_l2_n373(x) + else + fun_l2_n881(x) + end +end + +def fun_l1_n298(x) + if (x < 1) + fun_l2_n382(x) + else + fun_l2_n729(x) + end +end + +def fun_l1_n299(x) + if (x < 1) + fun_l2_n878(x) + else + fun_l2_n192(x) + end +end + +def fun_l1_n300(x) + if (x < 1) + fun_l2_n65(x) + else + fun_l2_n434(x) + end +end + +def fun_l1_n301(x) + if (x < 1) + fun_l2_n513(x) + else + fun_l2_n617(x) + end +end + +def fun_l1_n302(x) + if (x < 1) + fun_l2_n972(x) + else + fun_l2_n482(x) + end +end + +def fun_l1_n303(x) + if (x < 1) + fun_l2_n533(x) + else + fun_l2_n774(x) + end +end + +def fun_l1_n304(x) + if (x < 1) + fun_l2_n439(x) + else + fun_l2_n351(x) + end +end + +def fun_l1_n305(x) + if (x < 1) + fun_l2_n181(x) + else + fun_l2_n74(x) + end +end + +def fun_l1_n306(x) + if (x < 1) + fun_l2_n117(x) + else + fun_l2_n962(x) + end +end + +def fun_l1_n307(x) + if (x < 1) + fun_l2_n499(x) + else + fun_l2_n183(x) + end +end + +def fun_l1_n308(x) + if (x < 1) + fun_l2_n358(x) + else + fun_l2_n226(x) + end +end + +def fun_l1_n309(x) + if (x < 1) + fun_l2_n227(x) + else + fun_l2_n425(x) + end +end + +def fun_l1_n310(x) + if (x < 1) + fun_l2_n724(x) + else + fun_l2_n278(x) + end +end + +def fun_l1_n311(x) + if (x < 1) + fun_l2_n784(x) + else + fun_l2_n888(x) + end +end + +def fun_l1_n312(x) + if (x < 1) + fun_l2_n952(x) + else + fun_l2_n849(x) + end +end + +def fun_l1_n313(x) + if (x < 1) + fun_l2_n921(x) + else + fun_l2_n31(x) + end +end + +def fun_l1_n314(x) + if (x < 1) + fun_l2_n469(x) + else + fun_l2_n856(x) + end +end + +def fun_l1_n315(x) + if (x < 1) + fun_l2_n89(x) + else + fun_l2_n949(x) + end +end + +def fun_l1_n316(x) + if (x < 1) + fun_l2_n216(x) + else + fun_l2_n410(x) + end +end + +def fun_l1_n317(x) + if (x < 1) + fun_l2_n591(x) + else + fun_l2_n203(x) + end +end + +def fun_l1_n318(x) + if (x < 1) + fun_l2_n19(x) + else + fun_l2_n461(x) + end +end + +def fun_l1_n319(x) + if (x < 1) + fun_l2_n875(x) + else + fun_l2_n996(x) + end +end + +def fun_l1_n320(x) + if (x < 1) + fun_l2_n696(x) + else + fun_l2_n987(x) + end +end + +def fun_l1_n321(x) + if (x < 1) + fun_l2_n708(x) + else + fun_l2_n150(x) + end +end + +def fun_l1_n322(x) + if (x < 1) + fun_l2_n324(x) + else + fun_l2_n814(x) + end +end + +def fun_l1_n323(x) + if (x < 1) + fun_l2_n427(x) + else + fun_l2_n472(x) + end +end + +def fun_l1_n324(x) + if (x < 1) + fun_l2_n847(x) + else + fun_l2_n482(x) + end +end + +def fun_l1_n325(x) + if (x < 1) + fun_l2_n335(x) + else + fun_l2_n854(x) + end +end + +def fun_l1_n326(x) + if (x < 1) + fun_l2_n635(x) + else + fun_l2_n276(x) + end +end + +def fun_l1_n327(x) + if (x < 1) + fun_l2_n539(x) + else + fun_l2_n885(x) + end +end + +def fun_l1_n328(x) + if (x < 1) + fun_l2_n979(x) + else + fun_l2_n558(x) + end +end + +def fun_l1_n329(x) + if (x < 1) + fun_l2_n35(x) + else + fun_l2_n696(x) + end +end + +def fun_l1_n330(x) + if (x < 1) + fun_l2_n483(x) + else + fun_l2_n218(x) + end +end + +def fun_l1_n331(x) + if (x < 1) + fun_l2_n3(x) + else + fun_l2_n324(x) + end +end + +def fun_l1_n332(x) + if (x < 1) + fun_l2_n396(x) + else + fun_l2_n718(x) + end +end + +def fun_l1_n333(x) + if (x < 1) + fun_l2_n973(x) + else + fun_l2_n663(x) + end +end + +def fun_l1_n334(x) + if (x < 1) + fun_l2_n441(x) + else + fun_l2_n468(x) + end +end + +def fun_l1_n335(x) + if (x < 1) + fun_l2_n511(x) + else + fun_l2_n455(x) + end +end + +def fun_l1_n336(x) + if (x < 1) + fun_l2_n732(x) + else + fun_l2_n14(x) + end +end + +def fun_l1_n337(x) + if (x < 1) + fun_l2_n484(x) + else + fun_l2_n482(x) + end +end + +def fun_l1_n338(x) + if (x < 1) + fun_l2_n604(x) + else + fun_l2_n683(x) + end +end + +def fun_l1_n339(x) + if (x < 1) + fun_l2_n334(x) + else + fun_l2_n710(x) + end +end + +def fun_l1_n340(x) + if (x < 1) + fun_l2_n233(x) + else + fun_l2_n394(x) + end +end + +def fun_l1_n341(x) + if (x < 1) + fun_l2_n948(x) + else + fun_l2_n473(x) + end +end + +def fun_l1_n342(x) + if (x < 1) + fun_l2_n858(x) + else + fun_l2_n466(x) + end +end + +def fun_l1_n343(x) + if (x < 1) + fun_l2_n647(x) + else + fun_l2_n604(x) + end +end + +def fun_l1_n344(x) + if (x < 1) + fun_l2_n872(x) + else + fun_l2_n965(x) + end +end + +def fun_l1_n345(x) + if (x < 1) + fun_l2_n845(x) + else + fun_l2_n701(x) + end +end + +def fun_l1_n346(x) + if (x < 1) + fun_l2_n75(x) + else + fun_l2_n332(x) + end +end + +def fun_l1_n347(x) + if (x < 1) + fun_l2_n873(x) + else + fun_l2_n575(x) + end +end + +def fun_l1_n348(x) + if (x < 1) + fun_l2_n303(x) + else + fun_l2_n445(x) + end +end + +def fun_l1_n349(x) + if (x < 1) + fun_l2_n45(x) + else + fun_l2_n312(x) + end +end + +def fun_l1_n350(x) + if (x < 1) + fun_l2_n751(x) + else + fun_l2_n316(x) + end +end + +def fun_l1_n351(x) + if (x < 1) + fun_l2_n781(x) + else + fun_l2_n165(x) + end +end + +def fun_l1_n352(x) + if (x < 1) + fun_l2_n120(x) + else + fun_l2_n595(x) + end +end + +def fun_l1_n353(x) + if (x < 1) + fun_l2_n221(x) + else + fun_l2_n827(x) + end +end + +def fun_l1_n354(x) + if (x < 1) + fun_l2_n290(x) + else + fun_l2_n27(x) + end +end + +def fun_l1_n355(x) + if (x < 1) + fun_l2_n377(x) + else + fun_l2_n28(x) + end +end + +def fun_l1_n356(x) + if (x < 1) + fun_l2_n474(x) + else + fun_l2_n811(x) + end +end + +def fun_l1_n357(x) + if (x < 1) + fun_l2_n90(x) + else + fun_l2_n559(x) + end +end + +def fun_l1_n358(x) + if (x < 1) + fun_l2_n66(x) + else + fun_l2_n234(x) + end +end + +def fun_l1_n359(x) + if (x < 1) + fun_l2_n526(x) + else + fun_l2_n674(x) + end +end + +def fun_l1_n360(x) + if (x < 1) + fun_l2_n449(x) + else + fun_l2_n364(x) + end +end + +def fun_l1_n361(x) + if (x < 1) + fun_l2_n790(x) + else + fun_l2_n835(x) + end +end + +def fun_l1_n362(x) + if (x < 1) + fun_l2_n335(x) + else + fun_l2_n22(x) + end +end + +def fun_l1_n363(x) + if (x < 1) + fun_l2_n982(x) + else + fun_l2_n449(x) + end +end + +def fun_l1_n364(x) + if (x < 1) + fun_l2_n980(x) + else + fun_l2_n861(x) + end +end + +def fun_l1_n365(x) + if (x < 1) + fun_l2_n673(x) + else + fun_l2_n267(x) + end +end + +def fun_l1_n366(x) + if (x < 1) + fun_l2_n862(x) + else + fun_l2_n559(x) + end +end + +def fun_l1_n367(x) + if (x < 1) + fun_l2_n637(x) + else + fun_l2_n275(x) + end +end + +def fun_l1_n368(x) + if (x < 1) + fun_l2_n370(x) + else + fun_l2_n763(x) + end +end + +def fun_l1_n369(x) + if (x < 1) + fun_l2_n680(x) + else + fun_l2_n476(x) + end +end + +def fun_l1_n370(x) + if (x < 1) + fun_l2_n799(x) + else + fun_l2_n724(x) + end +end + +def fun_l1_n371(x) + if (x < 1) + fun_l2_n331(x) + else + fun_l2_n927(x) + end +end + +def fun_l1_n372(x) + if (x < 1) + fun_l2_n875(x) + else + fun_l2_n350(x) + end +end + +def fun_l1_n373(x) + if (x < 1) + fun_l2_n734(x) + else + fun_l2_n172(x) + end +end + +def fun_l1_n374(x) + if (x < 1) + fun_l2_n657(x) + else + fun_l2_n765(x) + end +end + +def fun_l1_n375(x) + if (x < 1) + fun_l2_n950(x) + else + fun_l2_n85(x) + end +end + +def fun_l1_n376(x) + if (x < 1) + fun_l2_n276(x) + else + fun_l2_n892(x) + end +end + +def fun_l1_n377(x) + if (x < 1) + fun_l2_n430(x) + else + fun_l2_n173(x) + end +end + +def fun_l1_n378(x) + if (x < 1) + fun_l2_n376(x) + else + fun_l2_n876(x) + end +end + +def fun_l1_n379(x) + if (x < 1) + fun_l2_n981(x) + else + fun_l2_n314(x) + end +end + +def fun_l1_n380(x) + if (x < 1) + fun_l2_n680(x) + else + fun_l2_n600(x) + end +end + +def fun_l1_n381(x) + if (x < 1) + fun_l2_n54(x) + else + fun_l2_n158(x) + end +end + +def fun_l1_n382(x) + if (x < 1) + fun_l2_n692(x) + else + fun_l2_n673(x) + end +end + +def fun_l1_n383(x) + if (x < 1) + fun_l2_n393(x) + else + fun_l2_n723(x) + end +end + +def fun_l1_n384(x) + if (x < 1) + fun_l2_n899(x) + else + fun_l2_n940(x) + end +end + +def fun_l1_n385(x) + if (x < 1) + fun_l2_n473(x) + else + fun_l2_n846(x) + end +end + +def fun_l1_n386(x) + if (x < 1) + fun_l2_n291(x) + else + fun_l2_n864(x) + end +end + +def fun_l1_n387(x) + if (x < 1) + fun_l2_n174(x) + else + fun_l2_n466(x) + end +end + +def fun_l1_n388(x) + if (x < 1) + fun_l2_n883(x) + else + fun_l2_n613(x) + end +end + +def fun_l1_n389(x) + if (x < 1) + fun_l2_n808(x) + else + fun_l2_n169(x) + end +end + +def fun_l1_n390(x) + if (x < 1) + fun_l2_n94(x) + else + fun_l2_n93(x) + end +end + +def fun_l1_n391(x) + if (x < 1) + fun_l2_n874(x) + else + fun_l2_n400(x) + end +end + +def fun_l1_n392(x) + if (x < 1) + fun_l2_n473(x) + else + fun_l2_n239(x) + end +end + +def fun_l1_n393(x) + if (x < 1) + fun_l2_n64(x) + else + fun_l2_n760(x) + end +end + +def fun_l1_n394(x) + if (x < 1) + fun_l2_n947(x) + else + fun_l2_n354(x) + end +end + +def fun_l1_n395(x) + if (x < 1) + fun_l2_n833(x) + else + fun_l2_n563(x) + end +end + +def fun_l1_n396(x) + if (x < 1) + fun_l2_n429(x) + else + fun_l2_n280(x) + end +end + +def fun_l1_n397(x) + if (x < 1) + fun_l2_n132(x) + else + fun_l2_n486(x) + end +end + +def fun_l1_n398(x) + if (x < 1) + fun_l2_n28(x) + else + fun_l2_n135(x) + end +end + +def fun_l1_n399(x) + if (x < 1) + fun_l2_n804(x) + else + fun_l2_n406(x) + end +end + +def fun_l1_n400(x) + if (x < 1) + fun_l2_n661(x) + else + fun_l2_n216(x) + end +end + +def fun_l1_n401(x) + if (x < 1) + fun_l2_n681(x) + else + fun_l2_n849(x) + end +end + +def fun_l1_n402(x) + if (x < 1) + fun_l2_n13(x) + else + fun_l2_n205(x) + end +end + +def fun_l1_n403(x) + if (x < 1) + fun_l2_n911(x) + else + fun_l2_n63(x) + end +end + +def fun_l1_n404(x) + if (x < 1) + fun_l2_n518(x) + else + fun_l2_n845(x) + end +end + +def fun_l1_n405(x) + if (x < 1) + fun_l2_n699(x) + else + fun_l2_n963(x) + end +end + +def fun_l1_n406(x) + if (x < 1) + fun_l2_n373(x) + else + fun_l2_n485(x) + end +end + +def fun_l1_n407(x) + if (x < 1) + fun_l2_n402(x) + else + fun_l2_n582(x) + end +end + +def fun_l1_n408(x) + if (x < 1) + fun_l2_n802(x) + else + fun_l2_n420(x) + end +end + +def fun_l1_n409(x) + if (x < 1) + fun_l2_n728(x) + else + fun_l2_n50(x) + end +end + +def fun_l1_n410(x) + if (x < 1) + fun_l2_n189(x) + else + fun_l2_n588(x) + end +end + +def fun_l1_n411(x) + if (x < 1) + fun_l2_n51(x) + else + fun_l2_n23(x) + end +end + +def fun_l1_n412(x) + if (x < 1) + fun_l2_n548(x) + else + fun_l2_n200(x) + end +end + +def fun_l1_n413(x) + if (x < 1) + fun_l2_n763(x) + else + fun_l2_n581(x) + end +end + +def fun_l1_n414(x) + if (x < 1) + fun_l2_n650(x) + else + fun_l2_n535(x) + end +end + +def fun_l1_n415(x) + if (x < 1) + fun_l2_n500(x) + else + fun_l2_n579(x) + end +end + +def fun_l1_n416(x) + if (x < 1) + fun_l2_n573(x) + else + fun_l2_n154(x) + end +end + +def fun_l1_n417(x) + if (x < 1) + fun_l2_n685(x) + else + fun_l2_n14(x) + end +end + +def fun_l1_n418(x) + if (x < 1) + fun_l2_n849(x) + else + fun_l2_n91(x) + end +end + +def fun_l1_n419(x) + if (x < 1) + fun_l2_n629(x) + else + fun_l2_n698(x) + end +end + +def fun_l1_n420(x) + if (x < 1) + fun_l2_n273(x) + else + fun_l2_n303(x) + end +end + +def fun_l1_n421(x) + if (x < 1) + fun_l2_n8(x) + else + fun_l2_n476(x) + end +end + +def fun_l1_n422(x) + if (x < 1) + fun_l2_n294(x) + else + fun_l2_n338(x) + end +end + +def fun_l1_n423(x) + if (x < 1) + fun_l2_n408(x) + else + fun_l2_n187(x) + end +end + +def fun_l1_n424(x) + if (x < 1) + fun_l2_n349(x) + else + fun_l2_n896(x) + end +end + +def fun_l1_n425(x) + if (x < 1) + fun_l2_n950(x) + else + fun_l2_n186(x) + end +end + +def fun_l1_n426(x) + if (x < 1) + fun_l2_n817(x) + else + fun_l2_n605(x) + end +end + +def fun_l1_n427(x) + if (x < 1) + fun_l2_n317(x) + else + fun_l2_n31(x) + end +end + +def fun_l1_n428(x) + if (x < 1) + fun_l2_n804(x) + else + fun_l2_n547(x) + end +end + +def fun_l1_n429(x) + if (x < 1) + fun_l2_n730(x) + else + fun_l2_n711(x) + end +end + +def fun_l1_n430(x) + if (x < 1) + fun_l2_n822(x) + else + fun_l2_n749(x) + end +end + +def fun_l1_n431(x) + if (x < 1) + fun_l2_n412(x) + else + fun_l2_n366(x) + end +end + +def fun_l1_n432(x) + if (x < 1) + fun_l2_n462(x) + else + fun_l2_n601(x) + end +end + +def fun_l1_n433(x) + if (x < 1) + fun_l2_n971(x) + else + fun_l2_n878(x) + end +end + +def fun_l1_n434(x) + if (x < 1) + fun_l2_n551(x) + else + fun_l2_n172(x) + end +end + +def fun_l1_n435(x) + if (x < 1) + fun_l2_n914(x) + else + fun_l2_n640(x) + end +end + +def fun_l1_n436(x) + if (x < 1) + fun_l2_n957(x) + else + fun_l2_n846(x) + end +end + +def fun_l1_n437(x) + if (x < 1) + fun_l2_n622(x) + else + fun_l2_n995(x) + end +end + +def fun_l1_n438(x) + if (x < 1) + fun_l2_n830(x) + else + fun_l2_n537(x) + end +end + +def fun_l1_n439(x) + if (x < 1) + fun_l2_n415(x) + else + fun_l2_n863(x) + end +end + +def fun_l1_n440(x) + if (x < 1) + fun_l2_n637(x) + else + fun_l2_n354(x) + end +end + +def fun_l1_n441(x) + if (x < 1) + fun_l2_n525(x) + else + fun_l2_n656(x) + end +end + +def fun_l1_n442(x) + if (x < 1) + fun_l2_n284(x) + else + fun_l2_n722(x) + end +end + +def fun_l1_n443(x) + if (x < 1) + fun_l2_n485(x) + else + fun_l2_n794(x) + end +end + +def fun_l1_n444(x) + if (x < 1) + fun_l2_n268(x) + else + fun_l2_n443(x) + end +end + +def fun_l1_n445(x) + if (x < 1) + fun_l2_n743(x) + else + fun_l2_n723(x) + end +end + +def fun_l1_n446(x) + if (x < 1) + fun_l2_n651(x) + else + fun_l2_n544(x) + end +end + +def fun_l1_n447(x) + if (x < 1) + fun_l2_n936(x) + else + fun_l2_n50(x) + end +end + +def fun_l1_n448(x) + if (x < 1) + fun_l2_n473(x) + else + fun_l2_n749(x) + end +end + +def fun_l1_n449(x) + if (x < 1) + fun_l2_n218(x) + else + fun_l2_n266(x) + end +end + +def fun_l1_n450(x) + if (x < 1) + fun_l2_n686(x) + else + fun_l2_n629(x) + end +end + +def fun_l1_n451(x) + if (x < 1) + fun_l2_n648(x) + else + fun_l2_n322(x) + end +end + +def fun_l1_n452(x) + if (x < 1) + fun_l2_n380(x) + else + fun_l2_n720(x) + end +end + +def fun_l1_n453(x) + if (x < 1) + fun_l2_n53(x) + else + fun_l2_n830(x) + end +end + +def fun_l1_n454(x) + if (x < 1) + fun_l2_n315(x) + else + fun_l2_n742(x) + end +end + +def fun_l1_n455(x) + if (x < 1) + fun_l2_n902(x) + else + fun_l2_n111(x) + end +end + +def fun_l1_n456(x) + if (x < 1) + fun_l2_n924(x) + else + fun_l2_n109(x) + end +end + +def fun_l1_n457(x) + if (x < 1) + fun_l2_n249(x) + else + fun_l2_n865(x) + end +end + +def fun_l1_n458(x) + if (x < 1) + fun_l2_n650(x) + else + fun_l2_n960(x) + end +end + +def fun_l1_n459(x) + if (x < 1) + fun_l2_n273(x) + else + fun_l2_n544(x) + end +end + +def fun_l1_n460(x) + if (x < 1) + fun_l2_n120(x) + else + fun_l2_n322(x) + end +end + +def fun_l1_n461(x) + if (x < 1) + fun_l2_n366(x) + else + fun_l2_n534(x) + end +end + +def fun_l1_n462(x) + if (x < 1) + fun_l2_n380(x) + else + fun_l2_n25(x) + end +end + +def fun_l1_n463(x) + if (x < 1) + fun_l2_n333(x) + else + fun_l2_n303(x) + end +end + +def fun_l1_n464(x) + if (x < 1) + fun_l2_n16(x) + else + fun_l2_n239(x) + end +end + +def fun_l1_n465(x) + if (x < 1) + fun_l2_n539(x) + else + fun_l2_n988(x) + end +end + +def fun_l1_n466(x) + if (x < 1) + fun_l2_n580(x) + else + fun_l2_n86(x) + end +end + +def fun_l1_n467(x) + if (x < 1) + fun_l2_n406(x) + else + fun_l2_n706(x) + end +end + +def fun_l1_n468(x) + if (x < 1) + fun_l2_n858(x) + else + fun_l2_n393(x) + end +end + +def fun_l1_n469(x) + if (x < 1) + fun_l2_n64(x) + else + fun_l2_n482(x) + end +end + +def fun_l1_n470(x) + if (x < 1) + fun_l2_n654(x) + else + fun_l2_n833(x) + end +end + +def fun_l1_n471(x) + if (x < 1) + fun_l2_n161(x) + else + fun_l2_n91(x) + end +end + +def fun_l1_n472(x) + if (x < 1) + fun_l2_n98(x) + else + fun_l2_n661(x) + end +end + +def fun_l1_n473(x) + if (x < 1) + fun_l2_n574(x) + else + fun_l2_n133(x) + end +end + +def fun_l1_n474(x) + if (x < 1) + fun_l2_n109(x) + else + fun_l2_n702(x) + end +end + +def fun_l1_n475(x) + if (x < 1) + fun_l2_n808(x) + else + fun_l2_n584(x) + end +end + +def fun_l1_n476(x) + if (x < 1) + fun_l2_n495(x) + else + fun_l2_n708(x) + end +end + +def fun_l1_n477(x) + if (x < 1) + fun_l2_n649(x) + else + fun_l2_n290(x) + end +end + +def fun_l1_n478(x) + if (x < 1) + fun_l2_n718(x) + else + fun_l2_n998(x) + end +end + +def fun_l1_n479(x) + if (x < 1) + fun_l2_n557(x) + else + fun_l2_n359(x) + end +end + +def fun_l1_n480(x) + if (x < 1) + fun_l2_n958(x) + else + fun_l2_n86(x) + end +end + +def fun_l1_n481(x) + if (x < 1) + fun_l2_n245(x) + else + fun_l2_n124(x) + end +end + +def fun_l1_n482(x) + if (x < 1) + fun_l2_n510(x) + else + fun_l2_n170(x) + end +end + +def fun_l1_n483(x) + if (x < 1) + fun_l2_n550(x) + else + fun_l2_n9(x) + end +end + +def fun_l1_n484(x) + if (x < 1) + fun_l2_n82(x) + else + fun_l2_n351(x) + end +end + +def fun_l1_n485(x) + if (x < 1) + fun_l2_n760(x) + else + fun_l2_n560(x) + end +end + +def fun_l1_n486(x) + if (x < 1) + fun_l2_n550(x) + else + fun_l2_n881(x) + end +end + +def fun_l1_n487(x) + if (x < 1) + fun_l2_n62(x) + else + fun_l2_n213(x) + end +end + +def fun_l1_n488(x) + if (x < 1) + fun_l2_n336(x) + else + fun_l2_n855(x) + end +end + +def fun_l1_n489(x) + if (x < 1) + fun_l2_n172(x) + else + fun_l2_n479(x) + end +end + +def fun_l1_n490(x) + if (x < 1) + fun_l2_n821(x) + else + fun_l2_n767(x) + end +end + +def fun_l1_n491(x) + if (x < 1) + fun_l2_n388(x) + else + fun_l2_n307(x) + end +end + +def fun_l1_n492(x) + if (x < 1) + fun_l2_n158(x) + else + fun_l2_n725(x) + end +end + +def fun_l1_n493(x) + if (x < 1) + fun_l2_n681(x) + else + fun_l2_n767(x) + end +end + +def fun_l1_n494(x) + if (x < 1) + fun_l2_n799(x) + else + fun_l2_n458(x) + end +end + +def fun_l1_n495(x) + if (x < 1) + fun_l2_n379(x) + else + fun_l2_n152(x) + end +end + +def fun_l1_n496(x) + if (x < 1) + fun_l2_n996(x) + else + fun_l2_n296(x) + end +end + +def fun_l1_n497(x) + if (x < 1) + fun_l2_n752(x) + else + fun_l2_n481(x) + end +end + +def fun_l1_n498(x) + if (x < 1) + fun_l2_n887(x) + else + fun_l2_n343(x) + end +end + +def fun_l1_n499(x) + if (x < 1) + fun_l2_n481(x) + else + fun_l2_n368(x) + end +end + +def fun_l1_n500(x) + if (x < 1) + fun_l2_n738(x) + else + fun_l2_n349(x) + end +end + +def fun_l1_n501(x) + if (x < 1) + fun_l2_n104(x) + else + fun_l2_n841(x) + end +end + +def fun_l1_n502(x) + if (x < 1) + fun_l2_n549(x) + else + fun_l2_n684(x) + end +end + +def fun_l1_n503(x) + if (x < 1) + fun_l2_n726(x) + else + fun_l2_n41(x) + end +end + +def fun_l1_n504(x) + if (x < 1) + fun_l2_n637(x) + else + fun_l2_n830(x) + end +end + +def fun_l1_n505(x) + if (x < 1) + fun_l2_n38(x) + else + fun_l2_n815(x) + end +end + +def fun_l1_n506(x) + if (x < 1) + fun_l2_n284(x) + else + fun_l2_n599(x) + end +end + +def fun_l1_n507(x) + if (x < 1) + fun_l2_n665(x) + else + fun_l2_n763(x) + end +end + +def fun_l1_n508(x) + if (x < 1) + fun_l2_n755(x) + else + fun_l2_n51(x) + end +end + +def fun_l1_n509(x) + if (x < 1) + fun_l2_n860(x) + else + fun_l2_n273(x) + end +end + +def fun_l1_n510(x) + if (x < 1) + fun_l2_n967(x) + else + fun_l2_n141(x) + end +end + +def fun_l1_n511(x) + if (x < 1) + fun_l2_n637(x) + else + fun_l2_n953(x) + end +end + +def fun_l1_n512(x) + if (x < 1) + fun_l2_n784(x) + else + fun_l2_n941(x) + end +end + +def fun_l1_n513(x) + if (x < 1) + fun_l2_n499(x) + else + fun_l2_n189(x) + end +end + +def fun_l1_n514(x) + if (x < 1) + fun_l2_n207(x) + else + fun_l2_n187(x) + end +end + +def fun_l1_n515(x) + if (x < 1) + fun_l2_n174(x) + else + fun_l2_n316(x) + end +end + +def fun_l1_n516(x) + if (x < 1) + fun_l2_n884(x) + else + fun_l2_n72(x) + end +end + +def fun_l1_n517(x) + if (x < 1) + fun_l2_n878(x) + else + fun_l2_n423(x) + end +end + +def fun_l1_n518(x) + if (x < 1) + fun_l2_n452(x) + else + fun_l2_n733(x) + end +end + +def fun_l1_n519(x) + if (x < 1) + fun_l2_n586(x) + else + fun_l2_n987(x) + end +end + +def fun_l1_n520(x) + if (x < 1) + fun_l2_n638(x) + else + fun_l2_n276(x) + end +end + +def fun_l1_n521(x) + if (x < 1) + fun_l2_n186(x) + else + fun_l2_n498(x) + end +end + +def fun_l1_n522(x) + if (x < 1) + fun_l2_n816(x) + else + fun_l2_n687(x) + end +end + +def fun_l1_n523(x) + if (x < 1) + fun_l2_n572(x) + else + fun_l2_n878(x) + end +end + +def fun_l1_n524(x) + if (x < 1) + fun_l2_n528(x) + else + fun_l2_n188(x) + end +end + +def fun_l1_n525(x) + if (x < 1) + fun_l2_n719(x) + else + fun_l2_n146(x) + end +end + +def fun_l1_n526(x) + if (x < 1) + fun_l2_n730(x) + else + fun_l2_n209(x) + end +end + +def fun_l1_n527(x) + if (x < 1) + fun_l2_n998(x) + else + fun_l2_n544(x) + end +end + +def fun_l1_n528(x) + if (x < 1) + fun_l2_n288(x) + else + fun_l2_n682(x) + end +end + +def fun_l1_n529(x) + if (x < 1) + fun_l2_n808(x) + else + fun_l2_n651(x) + end +end + +def fun_l1_n530(x) + if (x < 1) + fun_l2_n674(x) + else + fun_l2_n643(x) + end +end + +def fun_l1_n531(x) + if (x < 1) + fun_l2_n42(x) + else + fun_l2_n457(x) + end +end + +def fun_l1_n532(x) + if (x < 1) + fun_l2_n188(x) + else + fun_l2_n218(x) + end +end + +def fun_l1_n533(x) + if (x < 1) + fun_l2_n529(x) + else + fun_l2_n541(x) + end +end + +def fun_l1_n534(x) + if (x < 1) + fun_l2_n820(x) + else + fun_l2_n426(x) + end +end + +def fun_l1_n535(x) + if (x < 1) + fun_l2_n730(x) + else + fun_l2_n36(x) + end +end + +def fun_l1_n536(x) + if (x < 1) + fun_l2_n187(x) + else + fun_l2_n96(x) + end +end + +def fun_l1_n537(x) + if (x < 1) + fun_l2_n1(x) + else + fun_l2_n905(x) + end +end + +def fun_l1_n538(x) + if (x < 1) + fun_l2_n252(x) + else + fun_l2_n597(x) + end +end + +def fun_l1_n539(x) + if (x < 1) + fun_l2_n959(x) + else + fun_l2_n26(x) + end +end + +def fun_l1_n540(x) + if (x < 1) + fun_l2_n959(x) + else + fun_l2_n266(x) + end +end + +def fun_l1_n541(x) + if (x < 1) + fun_l2_n902(x) + else + fun_l2_n957(x) + end +end + +def fun_l1_n542(x) + if (x < 1) + fun_l2_n244(x) + else + fun_l2_n407(x) + end +end + +def fun_l1_n543(x) + if (x < 1) + fun_l2_n319(x) + else + fun_l2_n19(x) + end +end + +def fun_l1_n544(x) + if (x < 1) + fun_l2_n251(x) + else + fun_l2_n92(x) + end +end + +def fun_l1_n545(x) + if (x < 1) + fun_l2_n482(x) + else + fun_l2_n177(x) + end +end + +def fun_l1_n546(x) + if (x < 1) + fun_l2_n338(x) + else + fun_l2_n152(x) + end +end + +def fun_l1_n547(x) + if (x < 1) + fun_l2_n902(x) + else + fun_l2_n243(x) + end +end + +def fun_l1_n548(x) + if (x < 1) + fun_l2_n943(x) + else + fun_l2_n930(x) + end +end + +def fun_l1_n549(x) + if (x < 1) + fun_l2_n775(x) + else + fun_l2_n338(x) + end +end + +def fun_l1_n550(x) + if (x < 1) + fun_l2_n286(x) + else + fun_l2_n347(x) + end +end + +def fun_l1_n551(x) + if (x < 1) + fun_l2_n611(x) + else + fun_l2_n108(x) + end +end + +def fun_l1_n552(x) + if (x < 1) + fun_l2_n515(x) + else + fun_l2_n478(x) + end +end + +def fun_l1_n553(x) + if (x < 1) + fun_l2_n68(x) + else + fun_l2_n943(x) + end +end + +def fun_l1_n554(x) + if (x < 1) + fun_l2_n16(x) + else + fun_l2_n280(x) + end +end + +def fun_l1_n555(x) + if (x < 1) + fun_l2_n421(x) + else + fun_l2_n412(x) + end +end + +def fun_l1_n556(x) + if (x < 1) + fun_l2_n653(x) + else + fun_l2_n527(x) + end +end + +def fun_l1_n557(x) + if (x < 1) + fun_l2_n181(x) + else + fun_l2_n179(x) + end +end + +def fun_l1_n558(x) + if (x < 1) + fun_l2_n270(x) + else + fun_l2_n367(x) + end +end + +def fun_l1_n559(x) + if (x < 1) + fun_l2_n323(x) + else + fun_l2_n453(x) + end +end + +def fun_l1_n560(x) + if (x < 1) + fun_l2_n699(x) + else + fun_l2_n904(x) + end +end + +def fun_l1_n561(x) + if (x < 1) + fun_l2_n903(x) + else + fun_l2_n559(x) + end +end + +def fun_l1_n562(x) + if (x < 1) + fun_l2_n573(x) + else + fun_l2_n880(x) + end +end + +def fun_l1_n563(x) + if (x < 1) + fun_l2_n674(x) + else + fun_l2_n420(x) + end +end + +def fun_l1_n564(x) + if (x < 1) + fun_l2_n76(x) + else + fun_l2_n702(x) + end +end + +def fun_l1_n565(x) + if (x < 1) + fun_l2_n78(x) + else + fun_l2_n741(x) + end +end + +def fun_l1_n566(x) + if (x < 1) + fun_l2_n542(x) + else + fun_l2_n762(x) + end +end + +def fun_l1_n567(x) + if (x < 1) + fun_l2_n992(x) + else + fun_l2_n416(x) + end +end + +def fun_l1_n568(x) + if (x < 1) + fun_l2_n510(x) + else + fun_l2_n68(x) + end +end + +def fun_l1_n569(x) + if (x < 1) + fun_l2_n36(x) + else + fun_l2_n152(x) + end +end + +def fun_l1_n570(x) + if (x < 1) + fun_l2_n243(x) + else + fun_l2_n421(x) + end +end + +def fun_l1_n571(x) + if (x < 1) + fun_l2_n48(x) + else + fun_l2_n50(x) + end +end + +def fun_l1_n572(x) + if (x < 1) + fun_l2_n647(x) + else + fun_l2_n614(x) + end +end + +def fun_l1_n573(x) + if (x < 1) + fun_l2_n847(x) + else + fun_l2_n598(x) + end +end + +def fun_l1_n574(x) + if (x < 1) + fun_l2_n462(x) + else + fun_l2_n492(x) + end +end + +def fun_l1_n575(x) + if (x < 1) + fun_l2_n805(x) + else + fun_l2_n23(x) + end +end + +def fun_l1_n576(x) + if (x < 1) + fun_l2_n660(x) + else + fun_l2_n271(x) + end +end + +def fun_l1_n577(x) + if (x < 1) + fun_l2_n277(x) + else + fun_l2_n259(x) + end +end + +def fun_l1_n578(x) + if (x < 1) + fun_l2_n645(x) + else + fun_l2_n638(x) + end +end + +def fun_l1_n579(x) + if (x < 1) + fun_l2_n925(x) + else + fun_l2_n801(x) + end +end + +def fun_l1_n580(x) + if (x < 1) + fun_l2_n52(x) + else + fun_l2_n703(x) + end +end + +def fun_l1_n581(x) + if (x < 1) + fun_l2_n126(x) + else + fun_l2_n511(x) + end +end + +def fun_l1_n582(x) + if (x < 1) + fun_l2_n663(x) + else + fun_l2_n158(x) + end +end + +def fun_l1_n583(x) + if (x < 1) + fun_l2_n604(x) + else + fun_l2_n498(x) + end +end + +def fun_l1_n584(x) + if (x < 1) + fun_l2_n889(x) + else + fun_l2_n197(x) + end +end + +def fun_l1_n585(x) + if (x < 1) + fun_l2_n28(x) + else + fun_l2_n298(x) + end +end + +def fun_l1_n586(x) + if (x < 1) + fun_l2_n165(x) + else + fun_l2_n340(x) + end +end + +def fun_l1_n587(x) + if (x < 1) + fun_l2_n89(x) + else + fun_l2_n947(x) + end +end + +def fun_l1_n588(x) + if (x < 1) + fun_l2_n151(x) + else + fun_l2_n163(x) + end +end + +def fun_l1_n589(x) + if (x < 1) + fun_l2_n200(x) + else + fun_l2_n855(x) + end +end + +def fun_l1_n590(x) + if (x < 1) + fun_l2_n749(x) + else + fun_l2_n232(x) + end +end + +def fun_l1_n591(x) + if (x < 1) + fun_l2_n850(x) + else + fun_l2_n897(x) + end +end + +def fun_l1_n592(x) + if (x < 1) + fun_l2_n287(x) + else + fun_l2_n266(x) + end +end + +def fun_l1_n593(x) + if (x < 1) + fun_l2_n547(x) + else + fun_l2_n937(x) + end +end + +def fun_l1_n594(x) + if (x < 1) + fun_l2_n517(x) + else + fun_l2_n602(x) + end +end + +def fun_l1_n595(x) + if (x < 1) + fun_l2_n962(x) + else + fun_l2_n387(x) + end +end + +def fun_l1_n596(x) + if (x < 1) + fun_l2_n959(x) + else + fun_l2_n885(x) + end +end + +def fun_l1_n597(x) + if (x < 1) + fun_l2_n343(x) + else + fun_l2_n704(x) + end +end + +def fun_l1_n598(x) + if (x < 1) + fun_l2_n256(x) + else + fun_l2_n213(x) + end +end + +def fun_l1_n599(x) + if (x < 1) + fun_l2_n653(x) + else + fun_l2_n630(x) + end +end + +def fun_l1_n600(x) + if (x < 1) + fun_l2_n587(x) + else + fun_l2_n899(x) + end +end + +def fun_l1_n601(x) + if (x < 1) + fun_l2_n798(x) + else + fun_l2_n318(x) + end +end + +def fun_l1_n602(x) + if (x < 1) + fun_l2_n286(x) + else + fun_l2_n586(x) + end +end + +def fun_l1_n603(x) + if (x < 1) + fun_l2_n931(x) + else + fun_l2_n370(x) + end +end + +def fun_l1_n604(x) + if (x < 1) + fun_l2_n311(x) + else + fun_l2_n476(x) + end +end + +def fun_l1_n605(x) + if (x < 1) + fun_l2_n998(x) + else + fun_l2_n953(x) + end +end + +def fun_l1_n606(x) + if (x < 1) + fun_l2_n782(x) + else + fun_l2_n225(x) + end +end + +def fun_l1_n607(x) + if (x < 1) + fun_l2_n696(x) + else + fun_l2_n797(x) + end +end + +def fun_l1_n608(x) + if (x < 1) + fun_l2_n344(x) + else + fun_l2_n247(x) + end +end + +def fun_l1_n609(x) + if (x < 1) + fun_l2_n627(x) + else + fun_l2_n410(x) + end +end + +def fun_l1_n610(x) + if (x < 1) + fun_l2_n431(x) + else + fun_l2_n56(x) + end +end + +def fun_l1_n611(x) + if (x < 1) + fun_l2_n307(x) + else + fun_l2_n857(x) + end +end + +def fun_l1_n612(x) + if (x < 1) + fun_l2_n411(x) + else + fun_l2_n59(x) + end +end + +def fun_l1_n613(x) + if (x < 1) + fun_l2_n38(x) + else + fun_l2_n880(x) + end +end + +def fun_l1_n614(x) + if (x < 1) + fun_l2_n945(x) + else + fun_l2_n723(x) + end +end + +def fun_l1_n615(x) + if (x < 1) + fun_l2_n248(x) + else + fun_l2_n56(x) + end +end + +def fun_l1_n616(x) + if (x < 1) + fun_l2_n453(x) + else + fun_l2_n4(x) + end +end + +def fun_l1_n617(x) + if (x < 1) + fun_l2_n599(x) + else + fun_l2_n966(x) + end +end + +def fun_l1_n618(x) + if (x < 1) + fun_l2_n896(x) + else + fun_l2_n666(x) + end +end + +def fun_l1_n619(x) + if (x < 1) + fun_l2_n715(x) + else + fun_l2_n918(x) + end +end + +def fun_l1_n620(x) + if (x < 1) + fun_l2_n663(x) + else + fun_l2_n144(x) + end +end + +def fun_l1_n621(x) + if (x < 1) + fun_l2_n17(x) + else + fun_l2_n411(x) + end +end + +def fun_l1_n622(x) + if (x < 1) + fun_l2_n851(x) + else + fun_l2_n858(x) + end +end + +def fun_l1_n623(x) + if (x < 1) + fun_l2_n552(x) + else + fun_l2_n503(x) + end +end + +def fun_l1_n624(x) + if (x < 1) + fun_l2_n822(x) + else + fun_l2_n239(x) + end +end + +def fun_l1_n625(x) + if (x < 1) + fun_l2_n917(x) + else + fun_l2_n120(x) + end +end + +def fun_l1_n626(x) + if (x < 1) + fun_l2_n640(x) + else + fun_l2_n603(x) + end +end + +def fun_l1_n627(x) + if (x < 1) + fun_l2_n244(x) + else + fun_l2_n114(x) + end +end + +def fun_l1_n628(x) + if (x < 1) + fun_l2_n374(x) + else + fun_l2_n295(x) + end +end + +def fun_l1_n629(x) + if (x < 1) + fun_l2_n761(x) + else + fun_l2_n920(x) + end +end + +def fun_l1_n630(x) + if (x < 1) + fun_l2_n314(x) + else + fun_l2_n571(x) + end +end + +def fun_l1_n631(x) + if (x < 1) + fun_l2_n830(x) + else + fun_l2_n838(x) + end +end + +def fun_l1_n632(x) + if (x < 1) + fun_l2_n523(x) + else + fun_l2_n453(x) + end +end + +def fun_l1_n633(x) + if (x < 1) + fun_l2_n245(x) + else + fun_l2_n173(x) + end +end + +def fun_l1_n634(x) + if (x < 1) + fun_l2_n939(x) + else + fun_l2_n774(x) + end +end + +def fun_l1_n635(x) + if (x < 1) + fun_l2_n17(x) + else + fun_l2_n166(x) + end +end + +def fun_l1_n636(x) + if (x < 1) + fun_l2_n443(x) + else + fun_l2_n297(x) + end +end + +def fun_l1_n637(x) + if (x < 1) + fun_l2_n280(x) + else + fun_l2_n776(x) + end +end + +def fun_l1_n638(x) + if (x < 1) + fun_l2_n761(x) + else + fun_l2_n866(x) + end +end + +def fun_l1_n639(x) + if (x < 1) + fun_l2_n677(x) + else + fun_l2_n855(x) + end +end + +def fun_l1_n640(x) + if (x < 1) + fun_l2_n252(x) + else + fun_l2_n766(x) + end +end + +def fun_l1_n641(x) + if (x < 1) + fun_l2_n3(x) + else + fun_l2_n115(x) + end +end + +def fun_l1_n642(x) + if (x < 1) + fun_l2_n495(x) + else + fun_l2_n340(x) + end +end + +def fun_l1_n643(x) + if (x < 1) + fun_l2_n991(x) + else + fun_l2_n119(x) + end +end + +def fun_l1_n644(x) + if (x < 1) + fun_l2_n379(x) + else + fun_l2_n519(x) + end +end + +def fun_l1_n645(x) + if (x < 1) + fun_l2_n191(x) + else + fun_l2_n872(x) + end +end + +def fun_l1_n646(x) + if (x < 1) + fun_l2_n979(x) + else + fun_l2_n980(x) + end +end + +def fun_l1_n647(x) + if (x < 1) + fun_l2_n918(x) + else + fun_l2_n455(x) + end +end + +def fun_l1_n648(x) + if (x < 1) + fun_l2_n966(x) + else + fun_l2_n529(x) + end +end + +def fun_l1_n649(x) + if (x < 1) + fun_l2_n838(x) + else + fun_l2_n529(x) + end +end + +def fun_l1_n650(x) + if (x < 1) + fun_l2_n542(x) + else + fun_l2_n42(x) + end +end + +def fun_l1_n651(x) + if (x < 1) + fun_l2_n65(x) + else + fun_l2_n460(x) + end +end + +def fun_l1_n652(x) + if (x < 1) + fun_l2_n479(x) + else + fun_l2_n251(x) + end +end + +def fun_l1_n653(x) + if (x < 1) + fun_l2_n243(x) + else + fun_l2_n77(x) + end +end + +def fun_l1_n654(x) + if (x < 1) + fun_l2_n392(x) + else + fun_l2_n940(x) + end +end + +def fun_l1_n655(x) + if (x < 1) + fun_l2_n875(x) + else + fun_l2_n157(x) + end +end + +def fun_l1_n656(x) + if (x < 1) + fun_l2_n279(x) + else + fun_l2_n237(x) + end +end + +def fun_l1_n657(x) + if (x < 1) + fun_l2_n522(x) + else + fun_l2_n174(x) + end +end + +def fun_l1_n658(x) + if (x < 1) + fun_l2_n669(x) + else + fun_l2_n399(x) + end +end + +def fun_l1_n659(x) + if (x < 1) + fun_l2_n251(x) + else + fun_l2_n56(x) + end +end + +def fun_l1_n660(x) + if (x < 1) + fun_l2_n563(x) + else + fun_l2_n731(x) + end +end + +def fun_l1_n661(x) + if (x < 1) + fun_l2_n684(x) + else + fun_l2_n292(x) + end +end + +def fun_l1_n662(x) + if (x < 1) + fun_l2_n952(x) + else + fun_l2_n1(x) + end +end + +def fun_l1_n663(x) + if (x < 1) + fun_l2_n89(x) + else + fun_l2_n562(x) + end +end + +def fun_l1_n664(x) + if (x < 1) + fun_l2_n480(x) + else + fun_l2_n488(x) + end +end + +def fun_l1_n665(x) + if (x < 1) + fun_l2_n398(x) + else + fun_l2_n493(x) + end +end + +def fun_l1_n666(x) + if (x < 1) + fun_l2_n778(x) + else + fun_l2_n702(x) + end +end + +def fun_l1_n667(x) + if (x < 1) + fun_l2_n332(x) + else + fun_l2_n441(x) + end +end + +def fun_l1_n668(x) + if (x < 1) + fun_l2_n866(x) + else + fun_l2_n442(x) + end +end + +def fun_l1_n669(x) + if (x < 1) + fun_l2_n533(x) + else + fun_l2_n970(x) + end +end + +def fun_l1_n670(x) + if (x < 1) + fun_l2_n926(x) + else + fun_l2_n883(x) + end +end + +def fun_l1_n671(x) + if (x < 1) + fun_l2_n204(x) + else + fun_l2_n769(x) + end +end + +def fun_l1_n672(x) + if (x < 1) + fun_l2_n663(x) + else + fun_l2_n367(x) + end +end + +def fun_l1_n673(x) + if (x < 1) + fun_l2_n978(x) + else + fun_l2_n750(x) + end +end + +def fun_l1_n674(x) + if (x < 1) + fun_l2_n296(x) + else + fun_l2_n608(x) + end +end + +def fun_l1_n675(x) + if (x < 1) + fun_l2_n35(x) + else + fun_l2_n200(x) + end +end + +def fun_l1_n676(x) + if (x < 1) + fun_l2_n618(x) + else + fun_l2_n286(x) + end +end + +def fun_l1_n677(x) + if (x < 1) + fun_l2_n98(x) + else + fun_l2_n264(x) + end +end + +def fun_l1_n678(x) + if (x < 1) + fun_l2_n967(x) + else + fun_l2_n884(x) + end +end + +def fun_l1_n679(x) + if (x < 1) + fun_l2_n351(x) + else + fun_l2_n854(x) + end +end + +def fun_l1_n680(x) + if (x < 1) + fun_l2_n397(x) + else + fun_l2_n56(x) + end +end + +def fun_l1_n681(x) + if (x < 1) + fun_l2_n232(x) + else + fun_l2_n400(x) + end +end + +def fun_l1_n682(x) + if (x < 1) + fun_l2_n321(x) + else + fun_l2_n500(x) + end +end + +def fun_l1_n683(x) + if (x < 1) + fun_l2_n315(x) + else + fun_l2_n509(x) + end +end + +def fun_l1_n684(x) + if (x < 1) + fun_l2_n854(x) + else + fun_l2_n921(x) + end +end + +def fun_l1_n685(x) + if (x < 1) + fun_l2_n823(x) + else + fun_l2_n995(x) + end +end + +def fun_l1_n686(x) + if (x < 1) + fun_l2_n42(x) + else + fun_l2_n92(x) + end +end + +def fun_l1_n687(x) + if (x < 1) + fun_l2_n74(x) + else + fun_l2_n577(x) + end +end + +def fun_l1_n688(x) + if (x < 1) + fun_l2_n47(x) + else + fun_l2_n664(x) + end +end + +def fun_l1_n689(x) + if (x < 1) + fun_l2_n709(x) + else + fun_l2_n92(x) + end +end + +def fun_l1_n690(x) + if (x < 1) + fun_l2_n875(x) + else + fun_l2_n893(x) + end +end + +def fun_l1_n691(x) + if (x < 1) + fun_l2_n934(x) + else + fun_l2_n339(x) + end +end + +def fun_l1_n692(x) + if (x < 1) + fun_l2_n194(x) + else + fun_l2_n915(x) + end +end + +def fun_l1_n693(x) + if (x < 1) + fun_l2_n352(x) + else + fun_l2_n212(x) + end +end + +def fun_l1_n694(x) + if (x < 1) + fun_l2_n554(x) + else + fun_l2_n461(x) + end +end + +def fun_l1_n695(x) + if (x < 1) + fun_l2_n372(x) + else + fun_l2_n652(x) + end +end + +def fun_l1_n696(x) + if (x < 1) + fun_l2_n778(x) + else + fun_l2_n350(x) + end +end + +def fun_l1_n697(x) + if (x < 1) + fun_l2_n656(x) + else + fun_l2_n467(x) + end +end + +def fun_l1_n698(x) + if (x < 1) + fun_l2_n799(x) + else + fun_l2_n983(x) + end +end + +def fun_l1_n699(x) + if (x < 1) + fun_l2_n917(x) + else + fun_l2_n801(x) + end +end + +def fun_l1_n700(x) + if (x < 1) + fun_l2_n276(x) + else + fun_l2_n778(x) + end +end + +def fun_l1_n701(x) + if (x < 1) + fun_l2_n562(x) + else + fun_l2_n558(x) + end +end + +def fun_l1_n702(x) + if (x < 1) + fun_l2_n600(x) + else + fun_l2_n120(x) + end +end + +def fun_l1_n703(x) + if (x < 1) + fun_l2_n346(x) + else + fun_l2_n975(x) + end +end + +def fun_l1_n704(x) + if (x < 1) + fun_l2_n139(x) + else + fun_l2_n241(x) + end +end + +def fun_l1_n705(x) + if (x < 1) + fun_l2_n728(x) + else + fun_l2_n62(x) + end +end + +def fun_l1_n706(x) + if (x < 1) + fun_l2_n719(x) + else + fun_l2_n955(x) + end +end + +def fun_l1_n707(x) + if (x < 1) + fun_l2_n752(x) + else + fun_l2_n122(x) + end +end + +def fun_l1_n708(x) + if (x < 1) + fun_l2_n456(x) + else + fun_l2_n128(x) + end +end + +def fun_l1_n709(x) + if (x < 1) + fun_l2_n4(x) + else + fun_l2_n399(x) + end +end + +def fun_l1_n710(x) + if (x < 1) + fun_l2_n20(x) + else + fun_l2_n746(x) + end +end + +def fun_l1_n711(x) + if (x < 1) + fun_l2_n978(x) + else + fun_l2_n886(x) + end +end + +def fun_l1_n712(x) + if (x < 1) + fun_l2_n417(x) + else + fun_l2_n337(x) + end +end + +def fun_l1_n713(x) + if (x < 1) + fun_l2_n394(x) + else + fun_l2_n713(x) + end +end + +def fun_l1_n714(x) + if (x < 1) + fun_l2_n433(x) + else + fun_l2_n985(x) + end +end + +def fun_l1_n715(x) + if (x < 1) + fun_l2_n937(x) + else + fun_l2_n709(x) + end +end + +def fun_l1_n716(x) + if (x < 1) + fun_l2_n963(x) + else + fun_l2_n842(x) + end +end + +def fun_l1_n717(x) + if (x < 1) + fun_l2_n671(x) + else + fun_l2_n954(x) + end +end + +def fun_l1_n718(x) + if (x < 1) + fun_l2_n948(x) + else + fun_l2_n128(x) + end +end + +def fun_l1_n719(x) + if (x < 1) + fun_l2_n682(x) + else + fun_l2_n973(x) + end +end + +def fun_l1_n720(x) + if (x < 1) + fun_l2_n336(x) + else + fun_l2_n643(x) + end +end + +def fun_l1_n721(x) + if (x < 1) + fun_l2_n224(x) + else + fun_l2_n96(x) + end +end + +def fun_l1_n722(x) + if (x < 1) + fun_l2_n907(x) + else + fun_l2_n776(x) + end +end + +def fun_l1_n723(x) + if (x < 1) + fun_l2_n93(x) + else + fun_l2_n410(x) + end +end + +def fun_l1_n724(x) + if (x < 1) + fun_l2_n282(x) + else + fun_l2_n134(x) + end +end + +def fun_l1_n725(x) + if (x < 1) + fun_l2_n938(x) + else + fun_l2_n603(x) + end +end + +def fun_l1_n726(x) + if (x < 1) + fun_l2_n574(x) + else + fun_l2_n976(x) + end +end + +def fun_l1_n727(x) + if (x < 1) + fun_l2_n428(x) + else + fun_l2_n658(x) + end +end + +def fun_l1_n728(x) + if (x < 1) + fun_l2_n828(x) + else + fun_l2_n226(x) + end +end + +def fun_l1_n729(x) + if (x < 1) + fun_l2_n663(x) + else + fun_l2_n292(x) + end +end + +def fun_l1_n730(x) + if (x < 1) + fun_l2_n802(x) + else + fun_l2_n853(x) + end +end + +def fun_l1_n731(x) + if (x < 1) + fun_l2_n142(x) + else + fun_l2_n339(x) + end +end + +def fun_l1_n732(x) + if (x < 1) + fun_l2_n211(x) + else + fun_l2_n35(x) + end +end + +def fun_l1_n733(x) + if (x < 1) + fun_l2_n660(x) + else + fun_l2_n179(x) + end +end + +def fun_l1_n734(x) + if (x < 1) + fun_l2_n174(x) + else + fun_l2_n224(x) + end +end + +def fun_l1_n735(x) + if (x < 1) + fun_l2_n490(x) + else + fun_l2_n564(x) + end +end + +def fun_l1_n736(x) + if (x < 1) + fun_l2_n597(x) + else + fun_l2_n153(x) + end +end + +def fun_l1_n737(x) + if (x < 1) + fun_l2_n633(x) + else + fun_l2_n358(x) + end +end + +def fun_l1_n738(x) + if (x < 1) + fun_l2_n0(x) + else + fun_l2_n858(x) + end +end + +def fun_l1_n739(x) + if (x < 1) + fun_l2_n335(x) + else + fun_l2_n531(x) + end +end + +def fun_l1_n740(x) + if (x < 1) + fun_l2_n736(x) + else + fun_l2_n653(x) + end +end + +def fun_l1_n741(x) + if (x < 1) + fun_l2_n280(x) + else + fun_l2_n684(x) + end +end + +def fun_l1_n742(x) + if (x < 1) + fun_l2_n976(x) + else + fun_l2_n987(x) + end +end + +def fun_l1_n743(x) + if (x < 1) + fun_l2_n519(x) + else + fun_l2_n633(x) + end +end + +def fun_l1_n744(x) + if (x < 1) + fun_l2_n314(x) + else + fun_l2_n579(x) + end +end + +def fun_l1_n745(x) + if (x < 1) + fun_l2_n365(x) + else + fun_l2_n972(x) + end +end + +def fun_l1_n746(x) + if (x < 1) + fun_l2_n902(x) + else + fun_l2_n715(x) + end +end + +def fun_l1_n747(x) + if (x < 1) + fun_l2_n363(x) + else + fun_l2_n407(x) + end +end + +def fun_l1_n748(x) + if (x < 1) + fun_l2_n660(x) + else + fun_l2_n814(x) + end +end + +def fun_l1_n749(x) + if (x < 1) + fun_l2_n415(x) + else + fun_l2_n162(x) + end +end + +def fun_l1_n750(x) + if (x < 1) + fun_l2_n157(x) + else + fun_l2_n406(x) + end +end + +def fun_l1_n751(x) + if (x < 1) + fun_l2_n388(x) + else + fun_l2_n212(x) + end +end + +def fun_l1_n752(x) + if (x < 1) + fun_l2_n733(x) + else + fun_l2_n283(x) + end +end + +def fun_l1_n753(x) + if (x < 1) + fun_l2_n960(x) + else + fun_l2_n20(x) + end +end + +def fun_l1_n754(x) + if (x < 1) + fun_l2_n797(x) + else + fun_l2_n202(x) + end +end + +def fun_l1_n755(x) + if (x < 1) + fun_l2_n113(x) + else + fun_l2_n953(x) + end +end + +def fun_l1_n756(x) + if (x < 1) + fun_l2_n84(x) + else + fun_l2_n390(x) + end +end + +def fun_l1_n757(x) + if (x < 1) + fun_l2_n355(x) + else + fun_l2_n761(x) + end +end + +def fun_l1_n758(x) + if (x < 1) + fun_l2_n823(x) + else + fun_l2_n37(x) + end +end + +def fun_l1_n759(x) + if (x < 1) + fun_l2_n428(x) + else + fun_l2_n130(x) + end +end + +def fun_l1_n760(x) + if (x < 1) + fun_l2_n364(x) + else + fun_l2_n680(x) + end +end + +def fun_l1_n761(x) + if (x < 1) + fun_l2_n718(x) + else + fun_l2_n885(x) + end +end + +def fun_l1_n762(x) + if (x < 1) + fun_l2_n795(x) + else + fun_l2_n342(x) + end +end + +def fun_l1_n763(x) + if (x < 1) + fun_l2_n561(x) + else + fun_l2_n593(x) + end +end + +def fun_l1_n764(x) + if (x < 1) + fun_l2_n714(x) + else + fun_l2_n168(x) + end +end + +def fun_l1_n765(x) + if (x < 1) + fun_l2_n377(x) + else + fun_l2_n109(x) + end +end + +def fun_l1_n766(x) + if (x < 1) + fun_l2_n477(x) + else + fun_l2_n871(x) + end +end + +def fun_l1_n767(x) + if (x < 1) + fun_l2_n596(x) + else + fun_l2_n81(x) + end +end + +def fun_l1_n768(x) + if (x < 1) + fun_l2_n609(x) + else + fun_l2_n454(x) + end +end + +def fun_l1_n769(x) + if (x < 1) + fun_l2_n308(x) + else + fun_l2_n890(x) + end +end + +def fun_l1_n770(x) + if (x < 1) + fun_l2_n761(x) + else + fun_l2_n342(x) + end +end + +def fun_l1_n771(x) + if (x < 1) + fun_l2_n44(x) + else + fun_l2_n1(x) + end +end + +def fun_l1_n772(x) + if (x < 1) + fun_l2_n457(x) + else + fun_l2_n571(x) + end +end + +def fun_l1_n773(x) + if (x < 1) + fun_l2_n88(x) + else + fun_l2_n622(x) + end +end + +def fun_l1_n774(x) + if (x < 1) + fun_l2_n459(x) + else + fun_l2_n446(x) + end +end + +def fun_l1_n775(x) + if (x < 1) + fun_l2_n400(x) + else + fun_l2_n918(x) + end +end + +def fun_l1_n776(x) + if (x < 1) + fun_l2_n752(x) + else + fun_l2_n696(x) + end +end + +def fun_l1_n777(x) + if (x < 1) + fun_l2_n968(x) + else + fun_l2_n988(x) + end +end + +def fun_l1_n778(x) + if (x < 1) + fun_l2_n242(x) + else + fun_l2_n496(x) + end +end + +def fun_l1_n779(x) + if (x < 1) + fun_l2_n106(x) + else + fun_l2_n637(x) + end +end + +def fun_l1_n780(x) + if (x < 1) + fun_l2_n945(x) + else + fun_l2_n238(x) + end +end + +def fun_l1_n781(x) + if (x < 1) + fun_l2_n269(x) + else + fun_l2_n582(x) + end +end + +def fun_l1_n782(x) + if (x < 1) + fun_l2_n523(x) + else + fun_l2_n201(x) + end +end + +def fun_l1_n783(x) + if (x < 1) + fun_l2_n326(x) + else + fun_l2_n744(x) + end +end + +def fun_l1_n784(x) + if (x < 1) + fun_l2_n49(x) + else + fun_l2_n943(x) + end +end + +def fun_l1_n785(x) + if (x < 1) + fun_l2_n975(x) + else + fun_l2_n560(x) + end +end + +def fun_l1_n786(x) + if (x < 1) + fun_l2_n843(x) + else + fun_l2_n240(x) + end +end + +def fun_l1_n787(x) + if (x < 1) + fun_l2_n179(x) + else + fun_l2_n423(x) + end +end + +def fun_l1_n788(x) + if (x < 1) + fun_l2_n536(x) + else + fun_l2_n112(x) + end +end + +def fun_l1_n789(x) + if (x < 1) + fun_l2_n418(x) + else + fun_l2_n443(x) + end +end + +def fun_l1_n790(x) + if (x < 1) + fun_l2_n770(x) + else + fun_l2_n127(x) + end +end + +def fun_l1_n791(x) + if (x < 1) + fun_l2_n425(x) + else + fun_l2_n364(x) + end +end + +def fun_l1_n792(x) + if (x < 1) + fun_l2_n841(x) + else + fun_l2_n112(x) + end +end + +def fun_l1_n793(x) + if (x < 1) + fun_l2_n64(x) + else + fun_l2_n294(x) + end +end + +def fun_l1_n794(x) + if (x < 1) + fun_l2_n837(x) + else + fun_l2_n325(x) + end +end + +def fun_l1_n795(x) + if (x < 1) + fun_l2_n522(x) + else + fun_l2_n307(x) + end +end + +def fun_l1_n796(x) + if (x < 1) + fun_l2_n526(x) + else + fun_l2_n957(x) + end +end + +def fun_l1_n797(x) + if (x < 1) + fun_l2_n71(x) + else + fun_l2_n871(x) + end +end + +def fun_l1_n798(x) + if (x < 1) + fun_l2_n386(x) + else + fun_l2_n354(x) + end +end + +def fun_l1_n799(x) + if (x < 1) + fun_l2_n669(x) + else + fun_l2_n237(x) + end +end + +def fun_l1_n800(x) + if (x < 1) + fun_l2_n455(x) + else + fun_l2_n549(x) + end +end + +def fun_l1_n801(x) + if (x < 1) + fun_l2_n708(x) + else + fun_l2_n611(x) + end +end + +def fun_l1_n802(x) + if (x < 1) + fun_l2_n35(x) + else + fun_l2_n239(x) + end +end + +def fun_l1_n803(x) + if (x < 1) + fun_l2_n890(x) + else + fun_l2_n27(x) + end +end + +def fun_l1_n804(x) + if (x < 1) + fun_l2_n378(x) + else + fun_l2_n567(x) + end +end + +def fun_l1_n805(x) + if (x < 1) + fun_l2_n672(x) + else + fun_l2_n320(x) + end +end + +def fun_l1_n806(x) + if (x < 1) + fun_l2_n207(x) + else + fun_l2_n691(x) + end +end + +def fun_l1_n807(x) + if (x < 1) + fun_l2_n180(x) + else + fun_l2_n912(x) + end +end + +def fun_l1_n808(x) + if (x < 1) + fun_l2_n370(x) + else + fun_l2_n529(x) + end +end + +def fun_l1_n809(x) + if (x < 1) + fun_l2_n436(x) + else + fun_l2_n465(x) + end +end + +def fun_l1_n810(x) + if (x < 1) + fun_l2_n820(x) + else + fun_l2_n995(x) + end +end + +def fun_l1_n811(x) + if (x < 1) + fun_l2_n821(x) + else + fun_l2_n898(x) + end +end + +def fun_l1_n812(x) + if (x < 1) + fun_l2_n376(x) + else + fun_l2_n999(x) + end +end + +def fun_l1_n813(x) + if (x < 1) + fun_l2_n643(x) + else + fun_l2_n478(x) + end +end + +def fun_l1_n814(x) + if (x < 1) + fun_l2_n475(x) + else + fun_l2_n137(x) + end +end + +def fun_l1_n815(x) + if (x < 1) + fun_l2_n117(x) + else + fun_l2_n685(x) + end +end + +def fun_l1_n816(x) + if (x < 1) + fun_l2_n451(x) + else + fun_l2_n643(x) + end +end + +def fun_l1_n817(x) + if (x < 1) + fun_l2_n22(x) + else + fun_l2_n939(x) + end +end + +def fun_l1_n818(x) + if (x < 1) + fun_l2_n586(x) + else + fun_l2_n206(x) + end +end + +def fun_l1_n819(x) + if (x < 1) + fun_l2_n443(x) + else + fun_l2_n661(x) + end +end + +def fun_l1_n820(x) |