diff options
1513 files changed, 69456 insertions, 55639 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index 9ebe41e1c8..ea9b81aa47 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -11,19 +11,19 @@ clone_depth: 10 platform: - x64 skip_commits: - message: /^\[DOC\]/ + message: /\[DOC\]/ files: - doc/* - '**/*.md' - '**/*.rdoc' environment: ruby_version: "24-%Platform%" - zlib_version: "1.2.11" + zlib_version: "1.2.12" matrix: - build: vs vs: 120 ssl: OpenSSL - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 GEMS_FOR_TEST: "" - build: vs vs: 140 @@ -31,6 +31,8 @@ environment: APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 GEMS_FOR_TEST: "" RELINE_TEST_ENCODING: "UTF-8" +cache: + - c:\Tools\vcpkg\installed\ for: - matrix: @@ -42,6 +44,11 @@ for: - SET BITS=%Platform:x86=32% - SET BITS=%BITS:x=% - SET OPENSSL_DIR=C:\%ssl%-Win%BITS% + - cd C:\Tools\vcpkg + - git pull -q + - .\bootstrap-vcpkg.bat + - cd %APPVEYOR_BUILD_FOLDER% + - vcpkg --triplet %Platform%-windows install libffi libyaml readline zlib - CALL SET vcvars=%%^VS%VS%COMNTOOLS^%%..\..\VC\vcvarsall.bat - SET vcvars - '"%vcvars%" %Platform:x64=amd64%' @@ -65,12 +72,18 @@ for: - if not exist %ZLIB_ZIP% curl -fsSL -o %ZLIB_ZIP% --retry 10 https://zlib.net/zlib%zlib_version:.=%.zip - 7z x -aos -o%APPVEYOR_BUILD_FOLDER%\ext\zlib %ZLIB_ZIP% - for %%I in (%OPENSSL_DIR%\*.dll) do mklink /h \usr\local\bin\%%~nxI %%I + - for %%I in (c:\Tools\vcpkg\installed\%Platform%-windows\bin\*.dll) do ( + if not %%~nI == readline mklink \usr\local\bin\%%~nxI %%I + ) - attrib +r /s /d - mkdir %Platform%-mswin_%vs% build_script: - cd %APPVEYOR_BUILD_FOLDER% - cd %Platform%-mswin_%vs% - - ..\win32\configure.bat --without-ext=+,dbm,gdbm,readline --with-opt-dir=/usr/local --with-openssl-dir=%OPENSSL_DIR:\=/% + - >- + ..\win32\configure.bat + --with-opt-dir="/usr/local;c:/Tools/vcpkg/installed/%Platform%-windows" + --with-openssl-dir=%OPENSSL_DIR:\=/% - nmake -l - nmake install-nodoc - \usr\bin\ruby -v -e "p :locale => Encoding.find('locale'), :filesystem => Encoding.find('filesystem')" @@ -80,9 +93,26 @@ for: - set /a JOBS=%NUMBER_OF_PROCESSORS% - nmake -l "TESTOPTS=-v -q" btest - nmake -l "TESTOPTS=-v -q" test-basic - - nmake -l "TESTOPTS=-v --timeout-scale=3.0 --excludes=../test/excludes/_appveyor -j%JOBS% --exclude readline --exclude win32ole --exclude test_bignum --exclude test_syntax --exclude test_open-uri --exclude test_bundled_ca" test-all + - >- + nmake -l "TESTOPTS=-v --timeout-scale=3.0 + --excludes=../test/excludes/_appveyor -j%JOBS% + --exclude win32ole + --exclude test_bignum + --exclude test_syntax + --exclude test_open-uri + --exclude test_bundled_ca + " test-all # separately execute tests without -j which may crash worker with -j. - - nmake -l "TESTOPTS=-v --timeout-scale=3.0 --excludes=../test/excludes/_appveyor" test-all TESTS="../test/win32ole ../test/ruby/test_bignum.rb ../test/ruby/test_syntax.rb ../test/open-uri/test_open-uri.rb ../test/rubygems/test_bundled_ca.rb" + - >- + nmake -l + "TESTOPTS=-v --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 diff --git a/.cirrus.yml b/.cirrus.yml index c8fb326c89..0cab0023c2 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -12,7 +12,7 @@ task: name: Arm64 Graviton2 / $CC skip: "changesIncludeOnly('doc/**', '**.{md,rdoc}')" arm_container: - # We use the arm64 images at http://ghcr.io/ruby/ruby-ci-image . + # We use the arm64 images at https://github.com/ruby/ruby-ci-image/pkgs/container/ruby-ci-image . image: ghcr.io/ruby/ruby-ci-image:$CC # Define the used cpu core in each matrix task. We can use total 16 cpu # cores in entire matrix. [cpu] = [total cpu: 16] / [number of tasks] @@ -22,6 +22,7 @@ numeric.rb nilclass.rb pack.rb ractor.rb +string.rb timev.rb trace_point.rb warning.rb @@ -40,7 +41,6 @@ README.ja.md COPYING COPYING.ja -CONTRIBUTING.md LEGAL diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 15abc79af6..c8d7ec5e0d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,6 +4,7 @@ # YJIT sources and tests yjit* @maximecb @xrxr @tenderlove +yjit/* @maximecb @xrxr @tenderlove doc/yjit/* @maximecb @xrxr @tenderlove bootstraptest/test_yjit* @maximecb @xrxr @tenderlove test/ruby/test_yjit* @maximecb @xrxr @tenderlove diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..b18fd29357 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'weekly' diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index 4930028db0..1c314da911 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -20,7 +20,7 @@ jobs: baseruby: name: BASERUBY runs-on: ubuntu-20.04 - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} strategy: matrix: ruby: @@ -34,8 +34,8 @@ jobs: - ruby-3.1 steps: - - uses: actions/checkout@v2 - - uses: actions/cache@v2 + - uses: actions/checkout@v3 + - uses: actions/cache@v3 with: path: .downloaded-cache key: downloaded-cache @@ -44,7 +44,7 @@ jobs: ruby-version: ${{ matrix.ruby }} bundler: none - run: echo "GNUMAKEFLAGS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV - - run: sudo apt-get install build-essential autoconf bison + - run: sudo apt-get install build-essential autoconf bison libyaml-dev - run: ./autogen.sh - run: ./configure --disable-install-doc - run: make common-srcs diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index 2a8e6c244c..83f01d5868 100644 --- a/.github/workflows/bundled_gems.yml +++ b/.github/workflows/bundled_gems.yml @@ -28,9 +28,9 @@ jobs: echo "GNUMAKEFLAGS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV echo "TODAY=$(date +%F)" >> $GITHUB_ENV - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: .downloaded-cache key: downloaded-cache-${{ github.sha }} diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index 6c7e8e5787..6834d2c9c8 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -22,7 +22,7 @@ jobs: os: [ubuntu-20.04] fail-fast: true runs-on: ${{ matrix.os }} - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} steps: - name: Install libraries run: | @@ -39,8 +39,8 @@ jobs: run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@v2 - - uses: actions/cache@v2 + - uses: actions/checkout@v3 + - uses: actions/cache@v3 with: path: .downloaded-cache key: downloaded-cache diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 2872c96ffd..9e35d7f432 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -9,7 +9,7 @@ jobs: checks: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Check if C-sources are US-ASCII run: | ! grep -r -n '[^ -~]' *.[chy] include internal win32/*.[ch] @@ -23,7 +23,7 @@ jobs: done | grep -F . working-directory: include - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: .downloaded-cache key: downloaded-cache-${{ github.sha }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f81c79902d..299c6b220a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -23,7 +23,7 @@ jobs: # CodeQL runs on ubuntu-latest and windows-latest runs-on: ubuntu-latest - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} env: enable_install_doc: no @@ -36,9 +36,9 @@ jobs: sudo apt-get install --no-install-recommends -q -y build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev bison autoconf ruby - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: .downloaded-cache key: downloaded-cache @@ -47,7 +47,7 @@ jobs: run: sudo rm /usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: config-file: ./.github/codeql/codeql-config.yml @@ -55,7 +55,7 @@ jobs: run: echo "GNUMAKEFLAGS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index f3a872de8f..4ec02e2b59 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -16,16 +16,15 @@ concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} -# Github actions does not support YAML anchors. This creative use of +# GitHub actions does not support YAML anchors. This creative use of # environment variables (plus the "echo $GITHUB_ENV" hack) is to reroute that # restriction. env: default_cc: clang-14 append_cc: '' - crosshost: '' # -O1 is faster than -O3 in our tests... Majority of time are consumed trying - # to optimize binaries. Also Github Actions run on relatively modern CPUs + # to optimize binaries. Also GitHub Actions run on relatively modern CPUs # compared to, say, GCC 4 or Clang 3. We don't specify `-march=native` # because compilers tend not understand what the CPU is. optflags: '-O1' @@ -60,137 +59,162 @@ jobs: strategy: fail-fast: false matrix: + env: + - {} entry: - - { key: default_cc, name: gcc-11, value: gcc-11, container: gcc-11 } - - { key: default_cc, name: gcc-10, value: gcc-10, container: gcc-10 } - - { key: default_cc, name: gcc-9, value: gcc-9, container: gcc-9 } - - { key: default_cc, name: gcc-8, value: gcc-8, container: gcc-8 } - - { key: default_cc, name: gcc-7, value: gcc-7, container: gcc-7 } - - { key: default_cc, name: gcc-6, value: gcc-6, container: gcc-6 } - - { key: default_cc, name: gcc-5, value: gcc-5, container: gcc-5 } - - { key: default_cc, name: gcc-4.8, value: gcc-4.8, container: gcc-4.8 } - - key: default_cc - name: 'gcc-11 LTO' - value: 'gcc-11 -O2 -flto=auto -ffat-lto-objects' + - { 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-6, env: { default_cc: gcc-6 } } + - { name: gcc-5, env: { default_cc: gcc-5 } } + - { name: gcc-4.8, env: { default_cc: gcc-4.8 } } + - name: 'gcc-11 LTO' container: gcc-11 - shared: '--disable-shared' + env: + default_cc: 'gcc-11 -flto=auto -ffat-lto-objects' + optflags: '-O2' + shared: disable # check: true - - { key: default_cc, name: clang-14, value: clang-14, container: clang-14 } - - { key: default_cc, name: clang-13, value: clang-13, container: clang-13 } - - { key: default_cc, name: clang-12, value: clang-12, container: clang-12 } - - { key: default_cc, name: clang-11, value: clang-11, container: clang-11 } - - { key: default_cc, name: clang-10, value: clang-10, container: clang-10 } - - { key: default_cc, name: clang-9, value: clang-9, container: clang-9 } - - { key: default_cc, name: clang-8, value: clang-8, container: clang-8 } - - { key: default_cc, name: clang-7, value: clang-7, container: clang-7 } - - { key: default_cc, name: clang-6.0, value: clang-6.0, container: clang-6.0 } - - { key: default_cc, name: clang-5.0, value: clang-5.0, container: clang-5.0 } - - { key: default_cc, name: clang-4.0, value: clang-4.0, container: clang-4.0 } - - { key: default_cc, name: clang-3.9, value: clang-3.9, container: clang-3.9 } - - key: default_cc - name: 'clang-14 LTO' - value: 'clang-14 -O2 -flto=auto' + - name: 'gcc-11 annocheck' + container: gcc-11 + env: + # Minimal flags to pass the check. + default_cc: 'gcc-11 -O2 -fcf-protection -Wa,--generate-missing-build-notes=yes' + LDFLAGS: '-Wl,-z,now' + # FIXME: Drop skipping options + # https://bugs.ruby-lang.org/issues/18061 + # https://sourceware.org/annobin/annobin.html/Test-pie.html + TEST_ANNOCHECK_OPTS: "--skip-pie" + check: true + - { 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 } } + - { name: clang-9, env: { default_cc: clang-9 } } + - { name: clang-8, env: { default_cc: clang-8 } } + - { name: clang-7, env: { default_cc: clang-7 } } + - { name: clang-6.0, env: { default_cc: clang-6.0 } } + - { name: clang-5.0, env: { default_cc: clang-5.0 } } + - { name: clang-4.0, env: { default_cc: clang-4.0 } } + - { name: clang-3.9, env: { default_cc: clang-3.9 } } + - name: 'clang-14 LTO' container: clang-14 - shared: '--disable-shared' + env: + default_cc: 'clang-14 -flto=auto' + optflags: '-O2' + shared: disable # check: true - - { key: crosshost, name: aarch64-linux-gnu, value: aarch64-linux-gnu, container: crossbuild-essential-arm64 } -# - { key: crosshost, name: arm-linux-gnueabi, value: arm-linux-gnueabi } -# - { key: crosshost, name: arm-linux-gnueabihf, value: arm-linux-gnueabihf } -# - { key: crosshost, name: i686-w64-mingw32, value: i686-w64-mingw32 } -# - { key: crosshost, name: powerpc-linux-gnu, value: powerpc-linux-gnu } - - { key: crosshost, name: powerpc64le-linux-gnu, value: powerpc64le-linux-gnu, container: crossbuild-essential-ppc64el } - - { key: crosshost, name: s390x-linux-gnu, value: s390x-linux-gnu, container: crossbuild-essential-s390x } - - { key: crosshost, name: x86_64-w64-mingw32, value: x86_64-w64-mingw32, container: mingw-w64 } - - - { key: append_cc, name: c99, value: '-std=c99 -Werror=pedantic -pedantic-errors' } -# - { key: append_cc, name: c11, value: '-std=c11 -Werror=pedantic -pedantic-errors' } -# - { key: append_cc, name: c17, value: '-std=c17 -Werror=pedantic -pedantic-errors' } - - { key: append_cc, name: c2x, value: '-std=c2x -Werror=pedantic -pedantic-errors' } - - { key: CXXFLAGS, name: c++98, value: '-std=c++98 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } -# - { key: CXXFLAGS, name: c++11, value: '-std=c++11 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } -# - { key: CXXFLAGS, name: c++14, value: '-std=c++14 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } -# - { key: CXXFLAGS, name: c++17, value: '-std=c++17 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } - - { key: CXXFLAGS, name: c++2a, value: '-std=c++2a -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } - - - { key: optflags, name: '-O0', value: '-O0 -march=x86-64 -mtune=generic' } -# - { key: optflags, name: '-O3', value: '-O3 -march=x86-64 -mtune=generic', check: true } - - - { key: append_configure, name: gmp, value: '--with-gmp' } - - { key: append_configure, name: jemalloc, value: '--with-jemalloc' } - - { key: append_configure, name: valgrind, value: '--with-valgrind' } - - { key: append_configure, name: 'coroutine=ucontext', value: '--with-coroutine=ucontext' } - - { key: append_configure, name: 'coroutine=pthread', value: '--with-coroutine=pthread' } - - { key: append_configure, name: disable-jit-support, value: '--disable-jit-support' } - - { key: append_configure, name: disable-dln, value: '--disable-dln' } - - { key: append_configure, name: disable-rubygems, value: '--disable-rubygems' } - - - { key: cppflags, name: OPT_THREADED_CODE=1, value: '-DOPT_THREADED_CODE=1' } - - { key: cppflags, name: OPT_THREADED_CODE=2, value: '-DOPT_THREADED_CODE=2' } - - { key: cppflags, name: OPT_THREADED_CODE=3, value: '-DOPT_THREADED_CODE=3' } - - - { key: cppflags, name: NDEBUG, value: '-DNDEBUG' } - - { key: cppflags, name: RUBY_DEBUG, value: '-DRUBY_DEBUG' } -# - { key: cppflags, name: ARRAY_DEBUG, value: '-DARRAY_DEBUG' } -# - { key: cppflags, name: BIGNUM_DEBUG, value: '-DBIGNUM_DEBUG' } -# - { key: cppflags, name: CCAN_LIST_DEBUG, value: '-DCCAN_LIST_DEBUG' } -# - { key: cppflags, name: CPDEBUG=-1, value: '-DCPDEBUG=-1' } -# - { key: cppflags, name: ENC_DEBUG, value: '-DENC_DEBUG' } -# - { key: cppflags, name: GC_DEBUG, value: '-DGC_DEBUG' } -# - { key: cppflags, name: HASH_DEBUG, value: '-DHASH_DEBUG' } -# - { key: cppflags, name: ID_TABLE_DEBUG, value: '-DID_TABLE_DEBUG' } -# - { key: cppflags, name: RGENGC_DEBUG=-1, value: '-DRGENGC_DEBUG=-1' } -# - { key: cppflags, name: SYMBOL_DEBUG, value: '-DSYMBOL_DEBUG' } -# - { key: cppflags, name: THREAD_DEBUG=-1, value: '-DTHREAD_DEBUG=-1' } - -# - { key: cppflags, name: RGENGC_CHECK_MODE, value: '-DRGENGC_CHECK_MODE' } -# - { key: cppflags, name: TRANSIENT_HEAP_CHECK_MODE, value: '-DTRANSIENT_HEAP_CHECK_MODE' } -# - { key: cppflags, name: VM_CHECK_MODE, value: '-DVM_CHECK_MODE' } - - - { key: cppflags, name: USE_EMBED_CI=0, value: '-DUSE_EMBED_CI=0' } - - { key: cppflags, name: USE_FLONUM=0, value: '-DUSE_FLONUM=0' } -# - { key: cppflags, name: USE_GC_MALLOC_OBJ_INFO_DETAILS, value: '-DUSE_GC_MALLOC_OBJ_INFO_DETAILS' } - - { key: cppflags, name: USE_LAZY_LOAD, value: '-DUSE_LAZY_LOAD' } -# - { key: cppflags, name: USE_RINCGC=0, value: '-DUSE_RINCGC=0' } -# - { key: cppflags, name: USE_SYMBOL_GC=0, value: '-DUSE_SYMBOL_GC=0' } -# - { key: cppflags, name: USE_THREAD_CACHE=0, value: '-DUSE_THREAD_CACHE=0' } -# - { key: cppflags, name: USE_TRANSIENT_HEAP=0, value: '-DUSE_TRANSIENT_HEAP=0' } -# - { key: cppflags, name: USE_RUBY_DEBUG_LOG=1, value: '-DUSE_RUBY_DEBUG_LOG=1' } - - { key: cppflags, name: USE_RVARGC=0, value: '-DUSE_RVARGC=0' } -# - { key: cppflags, name: USE_RVARGC=1, value: '-DUSE_RVARGC=1' } - - - { key: cppflags, name: DEBUG_FIND_TIME_NUMGUESS, value: '-DDEBUG_FIND_TIME_NUMGUESS' } - - { key: cppflags, name: DEBUG_INTEGER_PACK, value: '-DDEBUG_INTEGER_PACK' } -# - { key: cppflags, name: ENABLE_PATH_CHECK, value: '-DENABLE_PATH_CHECK' } - - - { key: cppflags, name: GC_DEBUG_STRESS_TO_CLASS, value: '-DGC_DEBUG_STRESS_TO_CLASS' } -# - { key: cppflags, name: GC_ENABLE_LAZY_SWEEP=0, value: '-DGC_ENABLE_LAZY_SWEEP=0' } -# - { key: cppflags, name: GC_PROFILE_DETAIL_MEMOTY, value: '-DGC_PROFILE_DETAIL_MEMOTY' } -# - { key: cppflags, name: GC_PROFILE_MORE_DETAIL, value: '-DGC_PROFILE_MORE_DETAIL' } - -# - { key: cppflags, name: CALC_EXACT_MALLOC_SIZE, value: '-DCALC_EXACT_MALLOC_SIZE' } -# - { key: cppflags, name: MALLOC_ALLOCATED_SIZE_CHECK, value: '-DMALLOC_ALLOCATED_SIZE_CHECK' } - -# - { key: cppflags, name: IBF_ISEQ_ENABLE_LOCAL_BUFFER, value: '-DIBF_ISEQ_ENABLE_LOCAL_BUFFER' } - -# - { key: cppflags, name: RGENGC_ESTIMATE_OLDMALLOC, value: '-DRGENGC_ESTIMATE_OLDMALLOC' } -# - { key: cppflags, name: RGENGC_FORCE_MAJOR_GC, value: '-DRGENGC_FORCE_MAJOR_GC' } -# - { key: cppflags, name: RGENGC_OBJ_INFO, value: '-DRGENGC_OBJ_INFO' } -# - { key: cppflags, name: RGENGC_OLD_NEWOBJ_CHECK, value: '-DRGENGC_OLD_NEWOBJ_CHECK' } -# - { key: cppflags, name: RGENGC_PROFILE, value: '-DRGENGC_PROFILE' } - -# - { key: cppflags, name: VM_DEBUG_BP_CHECK, value: '-DVM_DEBUG_BP_CHECK' } -# - { key: cppflags, name: VM_DEBUG_VERIFY_METHOD_CACHE, value: '-DVM_DEBUG_VERIFY_METHOD_CACHE' } - - - { key: cppflags, name: MJIT_FORCE_ENABLE, value: '-DMJIT_FORCE_ENABLE' } - - { key: cppflags, name: YJIT_FORCE_ENABLE, value: '-DYJIT_FORCE_ENABLE' } +# - { name: aarch64-linux-gnu, crosshost: aarch64-linux-gnu, container: crossbuild-essential-arm64 } +# - { name: arm-linux-gnueabi, crosshost: arm-linux-gnueabi } +# - { name: arm-linux-gnueabihf, crosshost: arm-linux-gnueabihf } +# - { name: i686-w64-mingw32, crosshost: i686-w64-mingw32 } +# - { name: powerpc-linux-gnu, crosshost: powerpc-linux-gnu } +# - { name: powerpc64le-linux-gnu, crosshost: powerpc64le-linux-gnu, container: crossbuild-essential-ppc64el } +# - { name: s390x-linux-gnu, crosshost: s390x-linux-gnu, container: crossbuild-essential-s390x } +# - { name: x86_64-w64-mingw32, crosshost: x86_64-w64-mingw32, container: mingw-w64 } + + # -Wno-strict-prototypes is necessary with current clang-15 since + # older autoconf generate functions without prototype and -pedantic + # now implies strict-prototypes. Disabling the error but leaving the + # warning generates a lot of noise from use of ANYARGS in + # rb_define_method() and friends. + # See: https://github.com/llvm/llvm-project/commit/11da1b53d8cd3507959022cd790d5a7ad4573d94 + - { name: c99, env: { append_cc: '-std=c99 -Werror=pedantic -pedantic-errors -Wno-strict-prototypes' } } +# - { name: c11, env: { append_cc: '-std=c11 -Werror=pedantic -pedantic-errors -Wno-strict-prototypes' } } +# - { name: c17, env: { append_cc: '-std=c17 -Werror=pedantic -pedantic-errors -Wno-strict-prototypes' } } + - { name: c2x, env: { append_cc: '-std=c2x -Werror=pedantic -pedantic-errors -Wno-strict-prototypes' } } + - { name: c++98, env: { CXXFLAGS: '-std=c++98 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } +# - { name: c++11, env: { CXXFLAGS: '-std=c++11 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } +# - { name: c++14, env: { CXXFLAGS: '-std=c++14 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } +# - { name: c++17, env: { CXXFLAGS: '-std=c++17 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } + - { name: c++2a, env: { CXXFLAGS: '-std=c++2a -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' } } + + - { name: '-O0', env: { optflags: '-O0 -march=x86-64 -mtune=generic' } } +# - { name: '-O3', env: { optflags: '-O3 -march=x86-64 -mtune=generic' }, check: true } + + - { name: gmp, env: { append_configure: '--with-gmp' } } + - { name: jemalloc, env: { append_configure: '--with-jemalloc' } } + - { name: valgrind, env: { append_configure: '--with-valgrind' } } + - { name: 'coroutine=ucontext', env: { append_configure: '--with-coroutine=ucontext' } } + - { name: 'coroutine=pthread', env: { append_configure: '--with-coroutine=pthread' } } + - { name: disable-jit-support, env: { append_configure: '--disable-jit-support' } } + - { name: disable-dln, env: { append_configure: '--disable-dln' } } + - { name: enable-mkmf-verbose, env: { append_configure: '--enable-mkmf-verbose' } } + - { name: disable-rubygems, env: { append_configure: '--disable-rubygems' } } + + - { name: 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' } } +# - { name: USE_GC_MALLOC_OBJ_INFO_DETAILS, env: { cppflags: '-DUSE_GC_MALLOC_OBJ_INFO_DETAILS' } } + - { name: USE_LAZY_LOAD, env: { cppflags: '-DUSE_LAZY_LOAD' } } +# - { name: USE_RINCGC=0, env: { cppflags: '-DUSE_RINCGC=0' } } +# - { name: USE_SYMBOL_GC=0, env: { cppflags: '-DUSE_SYMBOL_GC=0' } } +# - { name: USE_THREAD_CACHE=0, env: { cppflags: '-DUSE_THREAD_CACHE=0' } } +# - { name: USE_TRANSIENT_HEAP=0, env: { cppflags: '-DUSE_TRANSIENT_HEAP=0' } } +# - { name: USE_RUBY_DEBUG_LOG=1, env: { cppflags: '-DUSE_RUBY_DEBUG_LOG=1' } } + - { name: USE_RVARGC=0, env: { cppflags: '-DUSE_RVARGC=0' } } +# - { name: USE_RVARGC=1, env: { cppflags: '-DUSE_RVARGC=1' } } +# - { name: USE_DEBUG_COUNTER, env: { cppflags: '-DUSE_DEBUG_COUNTER=1', RUBY_DEBUG_COUNTER_DISABLE: '1' } } + + - { name: DEBUG_FIND_TIME_NUMGUESS, env: { cppflags: '-DDEBUG_FIND_TIME_NUMGUESS' } } + - { name: DEBUG_INTEGER_PACK, env: { cppflags: '-DDEBUG_INTEGER_PACK' } } +# - { name: ENABLE_PATH_CHECK, env: { cppflags: '-DENABLE_PATH_CHECK' } } + + - { name: GC_DEBUG_STRESS_TO_CLASS, env: { cppflags: '-DGC_DEBUG_STRESS_TO_CLASS' } } +# - { name: GC_ENABLE_LAZY_SWEEP=0, env: { cppflags: '-DGC_ENABLE_LAZY_SWEEP=0' } } +# - { name: GC_PROFILE_DETAIL_MEMOTY, env: { cppflags: '-DGC_PROFILE_DETAIL_MEMOTY' } } +# - { name: GC_PROFILE_MORE_DETAIL, env: { cppflags: '-DGC_PROFILE_MORE_DETAIL' } } + +# - { name: CALC_EXACT_MALLOC_SIZE, env: { cppflags: '-DCALC_EXACT_MALLOC_SIZE' } } +# - { name: MALLOC_ALLOCATED_SIZE_CHECK, env: { cppflags: '-DMALLOC_ALLOCATED_SIZE_CHECK' } } + +# - { name: IBF_ISEQ_ENABLE_LOCAL_BUFFER, env: { cppflags: '-DIBF_ISEQ_ENABLE_LOCAL_BUFFER' } } + +# - { name: RGENGC_ESTIMATE_OLDMALLOC, env: { cppflags: '-DRGENGC_ESTIMATE_OLDMALLOC' } } +# - { name: RGENGC_FORCE_MAJOR_GC, env: { cppflags: '-DRGENGC_FORCE_MAJOR_GC' } } +# - { name: RGENGC_OBJ_INFO, env: { cppflags: '-DRGENGC_OBJ_INFO' } } +# - { name: RGENGC_OLD_NEWOBJ_CHECK, env: { cppflags: '-DRGENGC_OLD_NEWOBJ_CHECK' } } +# - { name: RGENGC_PROFILE, env: { cppflags: '-DRGENGC_PROFILE' } } + +# - { name: VM_DEBUG_BP_CHECK, env: { cppflags: '-DVM_DEBUG_BP_CHECK' } } +# - { name: VM_DEBUG_VERIFY_METHOD_CACHE, env: { cppflags: '-DVM_DEBUG_VERIFY_METHOD_CACHE' } } + + - { name: MJIT_FORCE_ENABLE, env: { cppflags: '-DMJIT_FORCE_ENABLE' } } + - { name: YJIT_FORCE_ENABLE, env: { cppflags: '-DYJIT_FORCE_ENABLE' } } name: ${{ matrix.entry.name }} runs-on: ubuntu-latest container: - image: ghcr.io/ruby/ruby-ci-image:${{ matrix.entry.container || 'clang-14' }} + image: ghcr.io/ruby/ruby-ci-image:${{ matrix.entry.container || matrix.entry.env.default_cc || 'clang-14' }} options: --user root - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + env: ${{ matrix.entry.env || matrix.env }} steps: - run: id working-directory: @@ -198,12 +222,11 @@ jobs: working-directory: - name: setenv run: | - echo "${{ matrix.entry.key }}=${{ matrix.entry.value }}" >> $GITHUB_ENV echo "GNUMAKEFLAGS=-sj$((1 + $(nproc --all)))" >> $GITHUB_ENV - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: path: src - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: src/.downloaded-cache key: downloaded-cache @@ -212,10 +235,15 @@ jobs: - name: Run configure run: > ../src/configure -C ${default_configure} ${append_configure} - ${{ matrix.entry.key == 'crosshost' && '--host="${crosshost}"' || '--with-gcc="${default_cc} ${append_cc}"' }} - ${{ matrix.entry.shared || '--enable-shared' }} + --${{ + matrix.entry.crosshost && 'host' || 'with-gcc' + }}=${{ + matrix.entry.crosshost || '"${default_cc}${append_cc:+ $append_cc}"' + }} + --${{ matrix.entry.shared || 'enable' }}-shared - run: make extract-extlibs - run: make incs + - run: make showflags - run: make - run: make leaked-globals - run: make test @@ -229,6 +257,8 @@ jobs: if: ${{ matrix.entry.check }} - run: make test-spec if: ${{ matrix.entry.check }} + - run: make test-annocheck + if: ${{ matrix.entry.check && endsWith(matrix.entry.name, 'annocheck') }} - uses: k0kubun/action-slack@v2.0.0 with: diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 3c531625e1..80b7a92f15 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -37,12 +37,14 @@ jobs: include: - msystem: "MINGW64" base_ruby: 2.6 - test_task: "check" # to make job names consistent + test_task: "check" + test-all-opts: "--name=!/TestObjSpace#test_reachable_objects_during_iteration/" - msystem: "UCRT64" base_ruby: head - test_task: "check" # to make job names consistent + test_task: "check" + test-all-opts: "--name=!/TestObjSpace#test_reachable_objects_during_iteration/" fail-fast: false - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} steps: - run: mkdir build working-directory: @@ -52,10 +54,10 @@ jobs: git config --global core.eol lf git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: path: src - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: src/.downloaded-cache key: downloaded-cache @@ -126,21 +128,26 @@ jobs: timeout-minutes: 5 run: | make test + if: ${{matrix.test_task == 'check' || matrix.test_task == 'test'}} - name: test-all timeout-minutes: 45 run: | # Actions uses UTF8, causes test failures, similar to normal OS setup chcp.com 437 - make test-all + make ${{ StartsWith(matrix.test_task, 'test/') && matrix.test_task || 'test-all' }} env: - RUBY_TESTOPTS: -j${{env.TEST_JOBS}} --retry --job-status=normal --show-skip --timeout-scale=1.5 + RUBY_TESTOPTS: >- + -j${{env.TEST_JOBS}} --retry --job-status=normal --show-skip --timeout-scale=1.5 + ${{ matrix.test-all-opts }} BUNDLER_VERSION: + if: ${{matrix.test_task == 'check' || matrix.test_task == 'test-all' || StartsWith(matrix.test_task, 'test/')}} - name: test-spec timeout-minutes: 10 run: | - make test-spec + make ${{ StartsWith(matrix.test_task, 'spec/') && matrix.test_task || 'test-spec' }} + if: ${{matrix.test_task == 'check' || matrix.test_task == 'test-spec' || StartsWith(matrix.test_task, 'spec/')}} - uses: k0kubun/action-slack@v2.0.0 with: diff --git a/.github/workflows/mjit.yml b/.github/workflows/mjit.yml index 75e5b1088c..c2479f9467 100644 --- a/.github/workflows/mjit.yml +++ b/.github/workflows/mjit.yml @@ -23,7 +23,7 @@ jobs: jit_opts: [ "--mjit", "--mjit-wait" ] fail-fast: false runs-on: ubuntu-latest - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} env: TESTOPTS: '-q --tty=no' RUN_OPTS: '--disable-gems ${{ matrix.jit_opts }} --mjit-debug=-ggdb3' @@ -40,10 +40,10 @@ jobs: run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: path: src - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: src/.downloaded-cache key: downloaded-cache diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index 06987a6cff..480731ad93 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -20,7 +20,7 @@ jobs: rubyspec: name: Rubyspec runs-on: ubuntu-20.04 - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} strategy: matrix: # Specs from ruby/spec should still run on all supported Ruby versions. @@ -30,7 +30,7 @@ jobs: - ruby-3.1 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index c01db1cc6c..21bc285d7d 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -26,7 +26,8 @@ jobs: configure: ["", "cppflags=-DRUBY_DEBUG"] include: - test_task: "check" - configure: "--host=i686-$OSTYPE" + configure: "" + arch: i686 - test_task: "check" configure: "--enable-shared --enable-load-relative" skipped_tests: "TestGem#test_.*_from_binstubs.*" @@ -36,8 +37,9 @@ jobs: env: GITPULLOPTIONS: --no-tags origin ${{github.ref}} RUBY_DEBUG: ci + SETARCH: ${{ matrix.arch && format('setarch {0}', matrix.arch) }} runs-on: ${{ matrix.os || 'ubuntu-20.04' }} - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} steps: - run: mkdir build working-directory: @@ -46,12 +48,11 @@ jobs: configure: ${{matrix.configure}} run: | echo "GNUMAKEFLAGS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV - arch=`echo " $configure" | sed '/.* --host=/!d;s///;s/[- ].*//'` - echo "SETARCH=${arch:+setarch $arch}" >> $GITHUB_ENV - name: Install libraries + env: + arch: ${{matrix.arch}} run: | set -x - arch="${SETARCH##* }" arch=${arch:+:${arch/i[3-6]86/i386}} ${arch:+sudo dpkg --add-architecture ${arch#:}} sudo apt-get update -q || : @@ -65,10 +66,10 @@ jobs: run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: path: src - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: src/.downloaded-cache key: downloaded-cache @@ -84,7 +85,7 @@ jobs: arch: ${{matrix.arch}} run: >- $SETARCH ../src/configure -C --disable-install-doc ${{ matrix.configure }} - ${arch:+--target=$arch-$OSTYPE} + ${arch:+--target=$arch-$OSTYPE --host=$arch-$OSTYPE} - run: $SETARCH make incs - run: $SETARCH make - run: $SETARCH make leaked-globals diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 4860b0d35e..83688fbaca 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -34,11 +34,10 @@ jobs: GITPULLOPTIONS: --no-tags origin ${{github.ref}} WASI_SDK_VERSION_MAJOR: 14 WASI_SDK_VERSION_MINOR: 0 - # Use older version, which uses glibc instead of musl, to avoid https://github.com/WebAssembly/binaryen/issues/4401 - BINARYEN_VERSION: 91 + BINARYEN_VERSION: 109 WASMTIME_VERSION: v0.33.0 runs-on: ubuntu-20.04 - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} steps: - run: mkdir build working-directory: @@ -46,7 +45,7 @@ jobs: run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: path: src - name: Install libraries @@ -67,10 +66,10 @@ jobs: 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-linux.tar.gz" + 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}/wasm-opt" /usr/local/bin/wasm-opt + sudo ln -fs "$PWD/binaryen-version_${BINARYEN_VERSION}/bin/wasm-opt" /usr/local/bin/wasm-opt working-directory: src - name: Set ENV run: | diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 574bfbf474..2c5b823d20 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -21,19 +21,15 @@ jobs: matrix: include: - vs: 2019 - os: windows-2019 - vcvars: '"C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat"' - # - vs: 2022 - # os: windows-2022 - # vcvars: '"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"' + - vs: 2022 fail-fast: false - runs-on: ${{ matrix.os }} - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + runs-on: windows-${{ matrix.vs < 2022 && '2019' || matrix.vs }} + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} name: VisualStudio ${{ matrix.vs }} env: GITPULLOPTIONS: --no-tags origin ${{github.ref}} - VCVARS: ${{ matrix.vcvars }} PATCH: C:\msys64\usr\bin\patch.exe + OS_VER: windows-${{ matrix.vs < 2022 && '2019' || matrix.vs }} steps: - run: md build working-directory: @@ -43,33 +39,39 @@ jobs: update: true install: >- patch - if: ${{ matrix.os != 'windows-2019' }} + if: ${{ env.OS_VER != 'windows-2019' }} - name: patch path shell: msys2 {0} run: echo PATCH=$(cygpath -wa $(command -v patch)) >> $GITHUB_ENV if: ${{ steps.setup-msys2.outcome == 'success' }} - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: C:\vcpkg\downloads - key: ${{ runner.os }}-vcpkg-download-${{ matrix.os }}-${{ github.sha }} + key: ${{ runner.os }}-vcpkg-download-${{ env.OS_VER }}-${{ github.sha }} restore-keys: | - ${{ runner.os }}-vcpkg-download-${{ matrix.os }}- + ${{ runner.os }}-vcpkg-download-${{ env.OS_VER }}- ${{ runner.os }}-vcpkg-download- + - uses: actions/cache@v3 + with: + path: C:\vcpkg\installed + key: ${{ runner.os }}-vcpkg-installed-${{ matrix.os }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-vcpkg-installed-${{ matrix.os }}- + ${{ runner.os }}-vcpkg-installed- - name: Install libraries with vcpkg run: | - vcpkg --triplet x64-windows install readline zlib - - uses: actions/cache@v2 + vcpkg --triplet x64-windows install libffi libyaml openssl readline zlib + - uses: actions/cache@v3 with: path: C:\Users\runneradmin\AppData\Local\Temp\chocolatey - key: ${{ runner.os }}-chocolatey-${{ matrix.os }}-${{ github.sha }} + key: ${{ runner.os }}-chocolatey-${{ env.OS_VER }}-${{ github.sha }} restore-keys: | - ${{ runner.os }}-chocolatey-${{ matrix.os }}- + ${{ runner.os }}-chocolatey-${{ env.OS_VER }}- ${{ runner.os }}-chocolatey- - name: Install libraries with chocolatey run: | # Using Choco-Install for retries, but it doesn't detect failures properly # if you pass multiple package names in a single command. - Choco-Install -PackageName openssl Choco-Install -PackageName winflexbison3 shell: pwsh - name: git config @@ -78,10 +80,10 @@ jobs: git config --global core.eol lf git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: path: src - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: src/.downloaded-cache key: downloaded-cache @@ -89,6 +91,12 @@ jobs: # %TEMP% is inconsistent with %TMP% and test-all expects they are consistent. # https://github.com/actions/virtual-environments/issues/712#issuecomment-613004302 run: | + set VS=${{ matrix.vs }} + set VCVARS=${{ matrix.vcvars }} + if not "%VCVARS%" == "" goto :vcset + set VCVARS="C:\Program Files (x86)\Microsoft Visual Studio\%VS%\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + if not exist %VCVARS% set VCVARS="C:\Program Files\Microsoft Visual Studio\%VS%\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + :vcset set | C:\msys64\usr\bin\sort > old.env call %VCVARS% set TMP=%USERPROFILE%\AppData\Local\Temp @@ -97,9 +105,18 @@ jobs: set | C:\msys64\usr\bin\sort > new.env C:\msys64\usr\bin\comm -13 old.env new.env >> %GITHUB_ENV% del *.env - - name: Configure + - name: link libraries run: | - ../src/win32/configure.bat --disable-install-doc --enable-bundled-libffi --with-opt-dir=C:/vcpkg/installed/x64-windows --with-openssl-dir="C:/Program Files/OpenSSL-Win64" + for %%I in (C:\vcpkg\installed\x64-windows\bin\*.dll) do ( + if not %%~nI == readline mklink %%~nxI %%I + ) + for %%I in (libcrypto-1_1-x64 libssl-1_1-x64) do ( + ren c:\Windows\System32\%%I.dll %%I.dll_ + ) + - name: Configure + run: >- + ../src/win32/configure.bat --disable-install-doc + --with-opt-dir=C:/vcpkg/installed/x64-windows - run: nmake incs - run: nmake extract-extlibs - run: nmake diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index b6fda32a54..5a86acb62c 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -16,34 +16,53 @@ concurrency: cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} jobs: + cargo: + name: Rust cargo test + # GitHub Action's image seems to already contain a Rust 1.58.0. + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + # For now we can't run cargo test --offline because it complains about the + # capstone dependency, even though the dependency is optional + #- run: cargo test --offline + - run: RUST_BACKTRACE=1 cargo test + working-directory: yjit + # Also compile and test with all features enabled + - run: RUST_BACKTRACE=1 cargo test --all-features + working-directory: yjit + # Check that we can build in release mode too + - run: cargo build --release + working-directory: yjit make: strategy: + fail-fast: false matrix: - test_task: ["check"] # "test-bundler-parallel", - os: - - ubuntu-20.04 -# - ubuntu-18.04 - yjit_opts: [ - "--yjit", - "--yjit --yjit-call-threshold=1", - ] - configure: ["", "cppflags=-DRUBY_DEBUG"] include: + - test_task: "check-yjit-bindings" + configure: "--with-gcc=clang-12 --enable-yjit=dev" + + - test_task: "check" + configure: "--enable-yjit RUSTC='rustc +1.58.1'" # release build + rust_version: "1.58.1" + + - test_task: "check" + configure: "--enable-yjit=dev" + + - test_task: "check" + configure: "--enable-yjit=dev" + yjit_opts: "--yjit-call-threshold=1" + - test_task: "test-all TESTS=--repeat-count=2" - os: ubuntu-20.04 - configure: "" - yjit_enable_env: RUBY_YJIT_ENABLE + configure: "--enable-yjit=dev" + - test_task: "test-bundled-gems" - os: ubuntu-20.04 - configure: "cppflags=-DRUBY_DEBUG" - yjit_enable_env: RUBY_YJIT_ENABLE - fail-fast: false + configure: "--enable-yjit=dev" env: GITPULLOPTIONS: --no-tags origin ${{github.ref}} RUN_OPTS: ${{ matrix.yjit_opts }} RUBY_DEBUG: ci - runs-on: ${{ matrix.os }} - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + runs-on: ubuntu-20.04 + if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} steps: - run: mkdir build working-directory: @@ -52,14 +71,17 @@ jobs: set -x sudo apt-get update -q || : sudo apt-get install --no-install-recommends -q -y build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev bison autoconf ruby + - name: Install Rust + if: ${{ matrix.rust_version }} + run: rustup install ${{ matrix.rust_version }} --profile minimal - name: git config run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: path: src - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: src/.downloaded-cache key: downloaded-cache @@ -76,7 +98,7 @@ jobs: - name: Run configure run: ../src/configure -C --disable-install-doc ${{ matrix.configure }} - run: make incs - - run: make + - run: make -j - run: make leaked-globals if: ${{ matrix.test_task == 'check' }} - run: make prepare-gems @@ -87,7 +109,6 @@ jobs: if: ${{ matrix.test_task == 'check' }} - name: Enable YJIT through ENV run: echo "RUBY_YJIT_ENABLE=1" >> $GITHUB_ENV - if: ${{ matrix.yjit_enable_env }} - run: make -s ${{ matrix.test_task }} RUN_OPTS="$RUN_OPTS" timeout-minutes: 60 env: diff --git a/.github/workflows/yjit_asm_tests.yml b/.github/workflows/yjit_asm_tests.yml deleted file mode 100644 index 8a9052dd41..0000000000 --- a/.github/workflows/yjit_asm_tests.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: YJIT x86 assembler tests - -on: - push: - paths-ignore: - - 'doc/**' - - '**.md' - - '**.rdoc' - pull_request: - paths-ignore: - - 'doc/**' - - '**.md' - - '**.rdoc' - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -jobs: - test: - runs-on: ubuntu-latest - if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} - steps: - - name: Install dependencies - run: | - set -x - sudo apt-get update -q || : - sudo apt-get install --no-install-recommends -q -y build-essential - - name: git config - run: | - git config --global advice.detachedHead 0 - git config --global init.defaultBranch garbage - - uses: actions/checkout@v2 - with: - path: src - - name: Run ASM tests - run: ./misc/test_yjit_asm.sh - working-directory: src diff --git a/.gitignore b/.gitignore index 675020fa91..521f4ec807 100644 --- a/.gitignore +++ b/.gitignore @@ -127,6 +127,7 @@ lcov*.info /ruby-runner /ruby-runner.h /ruby-man.rd.gz +/rubyspec_temp /run.gdb /sizes.c /static-ruby 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 index f8c66f715e..6875c766a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ language: c os: linux -if: commit_message !~ /^\[DOC\]/ +if: commit_message !~ /\[DOC\]/ dist: focal @@ -64,7 +64,6 @@ env: gcc-10 g++-10 libffi-dev - libgdbm-dev libncurses-dev libncursesw5-dev libreadline-dev @@ -110,7 +109,6 @@ env: libc6:armhf libstdc++-10-dev:armhf libffi-dev:armhf - libgdbm-dev:armhf libncurses-dev:armhf libncursesw5-dev:armhf libreadline-dev:armhf diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7363c106a2..13df6087ca 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1 @@ -Please see the [official issue tracker], [doc/contributing.rdoc] and wiki [HowToContribute]. - -[official issue tracker]: https://bugs.ruby-lang.org -[doc/contributing.rdoc]: contributing.rdoc -[HowToContribute]: https://bugs.ruby-lang.org/projects/ruby/wiki/HowToContribute +See ["Contributing to Ruby"](https://docs.ruby-lang.org/en/master/contributing_md.html), which includes setup and build instructions. @@ -20,16 +20,27 @@ Note that each entry is kept to a minimum, see links for details. end ``` +* A proc that accepts a single positional argument and keywords will + no longer autosplat. [[Bug #18633]] + + ```ruby + proc{|a, **k| a}.call([1, 2]) + # Ruby 3.1 and before + # => 1 + # Ruby 3.2 and after + # => [1, 2] + ``` + * Constant assignment evaluation order for constants set on explicit objects has been made consistent with single attribute assignment - evaluation order. With this code: + evaluation order. With this code: ```ruby foo::BAR = baz ``` - `foo` is now called before `baz`. Similarly, for multiple assignment - to constants, left-to-right evaluation order is used. With this + `foo` is now called before `baz`. Similarly, for multiple assignments + to constants, left-to-right evaluation order is used. With this code: ```ruby @@ -45,6 +56,40 @@ Note that each entry is kept to a minimum, see links for details. [[Bug #15928]] +* Find pattern is no longer experimental. + [[Feature #18585]] + +* Methods taking a rest parameter (like `*args`) and wishing to delegate keyword + arguments through `foo(*args)` must now be marked with `ruby2_keywords` + (if not already the case). In other words, all methods wishing to delegate + keyword arguments through `*args` must now be marked with `ruby2_keywords`, + with no exception. This will make it easier to transition to other ways of + delegation once a library can require Ruby 3+. Previously, the `ruby2_keywords` + flag was kept if the receiving method took `*args`, but this was a bug and an + inconsistency. A good technique to find the potentially-missing `ruby2_keywords` + is to run the test suite, for where it fails find the last method which must + receive keyword arguments, use `puts nil, caller, nil` there, and check each + method/block on the call chain which must delegate keywords is correctly marked + as `ruby2_keywords`. [[Bug #18625]] [[Bug #16466]] + + ```ruby + def target(**kw) + end + + # Accidentally worked without ruby2_keywords in Ruby 2.7-3.1, ruby2_keywords + # needed in 3.2+. Just like (*args, **kwargs) or (...) would be needed on + # both #foo and #bar when migrating away from ruby2_keywords. + ruby2_keywords def bar(*args) + target(*args) + end + + ruby2_keywords def foo(*args) + bar(*args) + end + + foo(k: 1) + ``` + ## Command line options ## Core classes updates @@ -56,28 +101,81 @@ Note: We're only listing outstanding class updates. empty, instead of returning the default value or calling the default proc. [[Bug #16908]] +* Kernel + * Kernel#binding raises RuntimeError if called from a non-Ruby frame + (such as a method defined in C). [[Bug #18487]] + +* MatchData + * MatchData#byteoffset has been added. [[Feature #13110]] + * Module * Module.used_refinements has been added. [[Feature #14332]] * Module#refinements has been added. [[Feature #12737]] * Module#const_added has been added. [[Feature #17881]] + * Module#undefined_instance_methods has been added. [[Feature #12655]] * Proc * Proc#dup returns an instance of subclass. [[Bug #17545]] + * Proc#parameters now accepts lambda keyword. [[Feature #15357]] + +* Regexp + * Regexp.new now supports passing the regexp flags not only as an Integer, + but also as a String Unknown flags raise errors. Otherwise, anything + other than `true`, `false`, `nil` or Integer will be warned. + [[Feature #18788]] * Refinement * Refinement#refined_class has been added. [[Feature #12737]] +* 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 14.0.0 and Emoji Version 14.0. [[Feature #18037]] + (also applies to Regexp) + * String#bytesplice has been added. [[Feature #18598]] + +* Struct + * A Struct class can also be initialized with keyword arguments + without `keyword_init: true` on `Struct.new` [[Feature #16806]] + +* 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 `target` and `target_line` keyword arguments are not + passed. [[Bug #16889]] + ## Stdlib updates -* The following default gem are updated. +* The following default gems are updated. * RubyGems 3.4.0.dev + * bigdecimal 3.1.2 * bundler 2.4.0.dev + * cgi 0.3.2 * etc 1.4.0 * io-console 0.5.11 + * io-nonblock 0.1.1 + * io-wait 0.3.0.pre + * ipaddr 1.2.4 + * json 2.6.2 + * logger 1.5.1 + * net-http 0.2.2 + * net-protocol 0.1.3 + * ostruct 0.5.5 + * psych 5.0.0.dev * reline 0.3.1 + * securerandom 0.2.0 + * stringio 3.0.3 + * timeout 0.3.0 * The following bundled gems are updated. + * minitest 5.16.2 * net-imap 0.2.3 - * typeprof 0.21.2 + * rbs 2.6.0 + * typeprof 0.21.3 + * debug 1.5.0 * The following default gems are now bundled gems. ## Compatibility issues @@ -88,6 +186,7 @@ Note: Excluding feature bug fixes. The following deprecated constants are removed. +* `Fixnum` and `Bignum` [[Feature #12005]] * `Random::DEFAULT` [[Feature #17351]] * `Struct::Group` * `Struct::Passwd` @@ -106,6 +205,10 @@ The following deprecated methods are removed. ## Stdlib compatibility issues +* `Psych` no longer bundles libyaml sources. + Users need to install the libyaml library themselves via the package + system. [[Feature #18571]] + ## C API updates ### Removed C APIs @@ -117,6 +220,8 @@ The following deprecated APIs are removed. ## Implementation improvements +* Fixed several race conditions in `Kernel#autoload`. [[Bug #18782]] + ## JIT ### MJIT @@ -137,14 +242,31 @@ The following deprecated APIs are removed. ## Miscellaneous changes +[Feature #12005]: https://bugs.ruby-lang.org/issues/12005 +[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 #16131]: https://bugs.ruby-lang.org/issues/16131 +[Bug #16466]: https://bugs.ruby-lang.org/issues/16466 +[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 [Feature #17881]: https://bugs.ruby-lang.org/issues/17881 +[Feature #18037]: https://bugs.ruby-lang.org/issues/18037 [Feature #18351]: https://bugs.ruby-lang.org/issues/18351 +[Bug #18487]: https://bugs.ruby-lang.org/issues/18487 +[Feature #18571]: https://bugs.ruby-lang.org/issues/18571 +[Feature #18585]: https://bugs.ruby-lang.org/issues/18585 +[Feature #18598]: https://bugs.ruby-lang.org/issues/18598 +[Bug #18625]: https://bugs.ruby-lang.org/issues/18625 +[Bug #18633]: https://bugs.ruby-lang.org/issues/18633 +[Bug #18782]: https://bugs.ruby-lang.org/issues/18782 +[Feature #18788]: https://bugs.ruby-lang.org/issues/18788 @@ -6,7 +6,7 @@ [](https://app.travis-ci.com/ruby/ruby) [](https://cirrus-ci.com/github/ruby/ruby/master) -# What's Ruby +# What is Ruby? Ruby is an interpreted object-oriented programming language often used for web development. It also offers many scripting features @@ -15,28 +15,25 @@ It is simple, straightforward, and extensible. ## Features of Ruby -* Simple Syntax -* **Normal** Object-oriented Features (e.g. class, method calls) -* **Advanced** Object-oriented Features (e.g. mix-in, singleton-method) -* Operator Overloading -* Exception Handling -* Iterators and Closures -* Garbage Collection -* Dynamic Loading of Object Files (on some architectures) -* Highly Portable (works on many Unix-like/POSIX compatible platforms as - well as Windows, macOS, etc.) cf. - https://github.com/ruby/ruby/blob/master/doc/maintainers.rdoc#label-Platform+Maintainers +* Simple Syntax +* **Normal** Object-oriented Features (e.g. class, method calls) +* **Advanced** Object-oriented Features (e.g. mix-in, singleton-method) +* Operator Overloading +* Exception Handling +* Iterators and Closures +* Garbage Collection +* Dynamic Loading of Object Files (on some architectures) +* Highly Portable (works on many Unix-like/POSIX compatible platforms as + well as Windows, macOS, etc.) cf. + https://github.com/ruby/ruby/blob/master/doc/maintainers.rdoc#label-Platform+Maintainers - -## How to get Ruby +## How to get Ruby with Git For a complete list of ways to install Ruby, including using third-party tools like rvm, see: https://www.ruby-lang.org/en/downloads/ -### Git - The mirror of the Ruby source tree can be checked out with the following command: $ git clone https://github.com/ruby/ruby.git @@ -49,22 +46,15 @@ to see the list of branches: You may also want to use https://git.ruby-lang.org/ruby.git (actual master of Ruby source) if you are a committer. -### Subversion - -Stable branches for older Ruby versions can be checked out with also the -following command: - - $ svn co https://svn.ruby-lang.org/repos/ruby/branches/ruby_2_6/ ruby - -Try the following command to see the list of branches: - - $ svn ls https://svn.ruby-lang.org/repos/ruby/branches/ - - ## Ruby home page https://www.ruby-lang.org/ +## Documentation + +- [English](https://docs.ruby-lang.org/en/master/index.html) +- [Japanese](https://docs.ruby-lang.org/ja/master/index.html) + ## Mailing list There is a mailing list to discuss Ruby. To subscribe to this list, please @@ -76,108 +66,20 @@ in the mail body (not subject) to the address [ruby-talk-request@ruby-lang.org]. [ruby-talk-request@ruby-lang.org]: mailto:ruby-talk-request@ruby-lang.org?subject=Join%20Ruby%20Mailing%20List&body=subscribe -## Requirements to build from repository - -1. GNU or BSD make -2. C99 compiler -3. autoconf 2.67 or higher -4. automake 1.15 or higher -5. bison 2.3 or higher -6. Ruby 2.2 or higher - -When building from a released version, only a C99 compiler and GNU or BSD make -is required. - -## How to compile and install - -1. If you want to use Microsoft Visual C++ to compile Ruby, read - [win32/README.win32](rdoc-ref:win32/README.win32) instead of this document. - -2. Run `./autogen.sh` to generate configure, when you build the source checked - out from the Git repository. - -3. Run `./configure`, which will generate `config.h` and `Makefile`. - - Some C compiler flags may be added by default depending on your - environment. Specify `optflags=..` and `warnflags=..` as necessary to - override them. - -4. Edit `include/ruby/defines.h` if you need. Usually this step will not be needed. - -5. Optional: Remove comment mark(`#`) before the module names from `ext/Setup`. - - This step is only necessary if you want to link modules statically. - - If you don't want to compile dynamic extensions (probably on architectures - which do not allow dynamic loading), remove comment mark from the line - "`#option nodynamic`" in `ext/Setup`. - - Usually this step will not be needed. - -6. Run `make`. - - * On Mac, set RUBY\_CODESIGN environment variable with a signing identity. - It uses the identity to sign `ruby` binary. See also codesign(1). - -7. Optionally, run '`make check`' to check whether the compiled Ruby - interpreter works well. If you see the message "`check succeeded`", your - Ruby works as it should (hopefully). - -8. Run '`make install`'. - - This command will create the following directories and install files into - them. - - * `${DESTDIR}${prefix}/bin` - * `${DESTDIR}${prefix}/include/ruby-${MAJOR}.${MINOR}.${TEENY}` - * `${DESTDIR}${prefix}/include/ruby-${MAJOR}.${MINOR}.${TEENY}/${PLATFORM}` - * `${DESTDIR}${prefix}/lib` - * `${DESTDIR}${prefix}/lib/ruby` - * `${DESTDIR}${prefix}/lib/ruby/${MAJOR}.${MINOR}.${TEENY}` - * `${DESTDIR}${prefix}/lib/ruby/${MAJOR}.${MINOR}.${TEENY}/${PLATFORM}` - * `${DESTDIR}${prefix}/lib/ruby/site_ruby` - * `${DESTDIR}${prefix}/lib/ruby/site_ruby/${MAJOR}.${MINOR}.${TEENY}` - * `${DESTDIR}${prefix}/lib/ruby/site_ruby/${MAJOR}.${MINOR}.${TEENY}/${PLATFORM}` - * `${DESTDIR}${prefix}/lib/ruby/vendor_ruby` - * `${DESTDIR}${prefix}/lib/ruby/vendor_ruby/${MAJOR}.${MINOR}.${TEENY}` - * `${DESTDIR}${prefix}/lib/ruby/vendor_ruby/${MAJOR}.${MINOR}.${TEENY}/${PLATFORM}` - * `${DESTDIR}${prefix}/lib/ruby/gems/${MAJOR}.${MINOR}.${TEENY}` - * `${DESTDIR}${prefix}/share/man/man1` - * `${DESTDIR}${prefix}/share/ri/${MAJOR}.${MINOR}.${TEENY}/system` - - - If Ruby's API version is '*x.y.z*', the `${MAJOR}` is '*x*', the - `${MINOR}` is '*y*', and the `${TEENY}` is '*z*'. - - **NOTE**: teeny of the API version may be different from one of Ruby's - program version - - You may have to be a super user to install Ruby. - -If you fail to compile Ruby, please send the detailed error report with the -error log and machine/OS type, to help others. - -Some extension libraries may not get compiled because of lack of necessary -external libraries and/or headers, then you will need to run '`make distclean-ext`' -to remove old configuration after installing them in such case. - ## Copying See the file [COPYING](rdoc-ref:COPYING). ## Feedback -Questions about the Ruby language can be asked on the [Ruby-Talk] mailing list +Questions about the Ruby language can be asked on the [Ruby-Talk](https://www.ruby-lang.org/en/community/mailing-lists) mailing list or on websites like https://stackoverflow.com. -Bugs should be reported at https://bugs.ruby-lang.org. Read [HowToReport] for more information. - -[Ruby-Talk]: https://www.ruby-lang.org/en/community/mailing-lists -[HowToReport]: https://bugs.ruby-lang.org/projects/ruby/wiki/HowToReport +Bugs should be reported at https://bugs.ruby-lang.org. Read ["Reporting Issues"](https://docs.ruby-lang.org/en/master/contributing/reporting_issues_md.html) for more information. ## Contributing -See the file [CONTRIBUTING.md](rdoc-ref:CONTRIBUTING) +See ["Contributing to Ruby"](https://docs.ruby-lang.org/en/master/contributing_md.html), which includes setup and build instructions. ## The Author diff --git a/addr2line.c b/addr2line.c index f660be9129..fe4ad84423 100644 --- a/addr2line.c +++ b/addr2line.c @@ -1292,7 +1292,7 @@ hexdump0(const unsigned char *p, size_t n) for (i=0; i < n; i++){ switch (i & 15) { case 0: - fprintf(stderr, "%02zd: %02X ", i/16, p[i]); + fprintf(stderr, "%02" PRIdSIZE ": %02X ", i/16, p[i]); break; case 15: fprintf(stderr, "%02X\n", p[i]); @@ -1313,16 +1313,16 @@ div_inspect(DebugInfoValue *v) { switch (v->type) { case VAL_uint: - fprintf(stderr,"%d: type:%d size:%zx v:%"PRIx64"\n",__LINE__,v->type,v->size,v->as.uint64); + fprintf(stderr,"%d: type:%d size:%" PRIxSIZE " v:%"PRIx64"\n",__LINE__,v->type,v->size,v->as.uint64); break; case VAL_int: - fprintf(stderr,"%d: type:%d size:%zx v:%"PRId64"\n",__LINE__,v->type,v->size,(int64_t)v->as.uint64); + fprintf(stderr,"%d: type:%d size:%" PRIxSIZE " v:%"PRId64"\n",__LINE__,v->type,v->size,(int64_t)v->as.uint64); break; case VAL_cstr: - fprintf(stderr,"%d: type:%d size:%zx v:'%s'\n",__LINE__,v->type,v->size,v->as.ptr); + fprintf(stderr,"%d: type:%d size:%" PRIxSIZE " v:'%s'\n",__LINE__,v->type,v->size,v->as.ptr); break; case VAL_data: - fprintf(stderr,"%d: type:%d size:%zx v:\n",__LINE__,v->type,v->size); + fprintf(stderr,"%d: type:%d size:%" PRIxSIZE " v:\n",__LINE__,v->type,v->size); hexdump(v->as.ptr, 16); break; } @@ -2257,9 +2257,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); @@ -2326,8 +2329,8 @@ rb_dump_backtrace_with_lines(int num_traces, void **traces) /* if the binary is strip-ed, this may effect */ for (p=dladdr_fbases; *p; p++) { if (*p == info.dli_fbase) { - lines[i].path = info.dli_fname; - lines[i].sname = info.dli_sname; + if (info.dli_fname) lines[i].path = info.dli_fname; + if (info.dli_sname) lines[i].sname = info.dli_sname; goto next_line; } } @@ -2337,9 +2340,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; @@ -139,7 +139,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)); \ @@ -157,7 +157,7 @@ should_not_be_shared_and_embedded(VALUE ary) assert(ARY_SHARED_ROOT_P(_value_)); \ RB_OBJ_WRITE(_ary_, &RARRAY(_ary_)->as.heap.aux.shared_root, _value_); \ } while (0) -#define RARRAY_SHARED_ROOT_FLAG FL_USER5 +#define RARRAY_SHARED_ROOT_FLAG FL_USER12 #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) \ @@ -165,6 +165,7 @@ should_not_be_shared_and_embedded(VALUE ary) #define ARY_SHARED_ROOT_OCCUPIED(ary) (ARY_SHARED_ROOT_REFCNT(ary) == 1) #define ARY_SET_SHARED_ROOT_REFCNT(ary, value) do { \ assert(ARY_SHARED_ROOT_P(ary)); \ + assert((value) >= 0); \ RARRAY(ary)->as.heap.aux.capa = (value); \ } while (0) #define FL_SET_SHARED_ROOT(ary) do { \ @@ -183,6 +184,34 @@ 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 +} + #if ARRAY_DEBUG #define ary_verify(ary) ary_verify_(ary, __FILE__, __LINE__) @@ -204,7 +233,7 @@ ary_verify_(VALUE ary, const char *file, int line) 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 @@ -446,7 +475,7 @@ ary_resize_capa(VALUE ary, long capacity) assert(!OBJ_FROZEN(ary)); assert(!ARY_SHARED_P(ary)); - if (capacity > RARRAY_EMBED_LEN_MAX) { + if (capacity > ary_embed_capa(ary)) { size_t new_capa = capacity; if (ARY_EMBED_P(ary)) { long len = ARY_EMBED_LEN(ary); @@ -512,12 +541,8 @@ ary_double_capa(VALUE ary, long min) static void rb_ary_decrement_share(VALUE shared_root) { - if (shared_root) { - long num = ARY_SHARED_ROOT_REFCNT(shared_root) - 1; - if (num > 0) { - ARY_SET_SHARED_ROOT_REFCNT(shared_root, num); - } - } + long num = ARY_SHARED_ROOT_REFCNT(shared_root); + ARY_SET_SHARED_ROOT_REFCNT(shared_root, num - 1); } static void @@ -528,21 +553,26 @@ rb_ary_unshare(VALUE ary) 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) { - ARY_SET_SHARED_ROOT_REFCNT(shared_root, num + 1); - } + assert(num >= 0); + ARY_SET_SHARED_ROOT_REFCNT(shared_root, num + 1); return shared_root; } @@ -571,7 +601,7 @@ rb_ary_cancel_sharing(VALUE ary) ary_verify(shared_root); - if (len <= RARRAY_EMBED_LEN_MAX) { + if (len <= ary_embed_capa(ary)) { const VALUE *ptr = ARY_HEAP_PTR(ary); FL_UNSET_SHARED(ary); FL_SET_EMBED(ary); @@ -621,7 +651,7 @@ ary_ensure_room_for_push(VALUE ary, long add_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)) { @@ -663,6 +693,7 @@ ary_ensure_room_for_push(VALUE ary, long add_len) * array.freeze -> self * * Freezes +self+; returns +self+: + * * a = [] * a.frozen? # => false * a.freeze @@ -697,9 +728,16 @@ rb_ary_shared_with_p(VALUE ary1, VALUE ary2) } static VALUE -ary_alloc(VALUE klass) +ary_alloc_embed(VALUE klass, long capa) { - NEWOBJ_OF(ary, struct RArray, klass, T_ARRAY | RARRAY_EMBED_FLAG | (RGENGC_WB_PROTECTED_ARRAY ? FL_WB_PROTECTED : 0)); + size_t size = ary_embed_size(capa); + assert(rb_gc_size_allocatable_p(size)); +#if !USE_RVARGC + assert(size <= sizeof(struct RArray)); +#endif + RVARGC_NEWOBJ_OF(ary, struct RArray, klass, + T_ARRAY | RARRAY_EMBED_FLAG | (RGENGC_WB_PROTECTED_ARRAY ? FL_WB_PROTECTED : 0), + size); /* Created array is: * FL_SET_EMBED((VALUE)ary); * ARY_SET_EMBED_LEN((VALUE)ary, 0); @@ -708,10 +746,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 @@ -728,10 +775,14 @@ ary_new(VALUE klass, long capa) 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); + assert(!ARY_EMBED_P(ary)); + ptr = ary_heap_alloc(ary, capa); - FL_UNSET_EMBED(ary); ARY_SET_PTR(ary, ptr); ARY_SET_CAPA(ary, capa); ARY_SET_HEAP_LEN(ary, 0); @@ -749,7 +800,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 @@ -792,9 +843,16 @@ rb_ary_new_from_values(long n, const VALUE *elts) } static VALUE -ec_ary_alloc(rb_execution_context_t *ec, VALUE klass) +ec_ary_alloc_embed(rb_execution_context_t *ec, VALUE klass, long capa) { - RB_EC_NEWOBJ_OF(ec, ary, struct RArray, klass, T_ARRAY | RARRAY_EMBED_FLAG | (RGENGC_WB_PROTECTED_ARRAY ? FL_WB_PROTECTED : 0)); + size_t size = ary_embed_size(capa); + assert(rb_gc_size_allocatable_p(size)); +#if !USE_RVARGC + assert(size <= sizeof(struct RArray)); +#endif + RB_RVARGC_EC_NEWOBJ_OF(ec, ary, struct RArray, klass, + T_ARRAY | RARRAY_EMBED_FLAG | (RGENGC_WB_PROTECTED_ARRAY ? FL_WB_PROTECTED : 0), + size); /* Created array is: * FL_SET_EMBED((VALUE)ary); * ARY_SET_EMBED_LEN((VALUE)ary, 0); @@ -803,6 +861,15 @@ ec_ary_alloc(rb_execution_context_t *ec, VALUE klass) } static VALUE +ec_ary_alloc_heap(rb_execution_context_t *ec, VALUE klass) +{ + RB_RVARGC_EC_NEWOBJ_OF(ec, ary, struct RArray, klass, + T_ARRAY | (RGENGC_WB_PROTECTED_ARRAY ? FL_WB_PROTECTED : 0), + sizeof(struct RArray)); + return (VALUE)ary; +} + +static VALUE ec_ary_new(rb_execution_context_t *ec, VALUE klass, long capa) { VALUE ary,*ptr; @@ -816,11 +883,14 @@ ec_ary_new(rb_execution_context_t *ec, VALUE klass, long capa) RUBY_DTRACE_CREATE_HOOK(ARRAY, capa); - ary = ec_ary_alloc(ec, klass); + if (ary_embeddable_p(capa)) { + ary = ec_ary_alloc_embed(ec, klass, capa); + } + else { + ary = ec_ary_alloc_heap(ec, klass); + assert(!ARY_EMBED_P(ary)); - if (capa > RARRAY_EMBED_LEN_MAX) { ptr = ary_heap_alloc(ary, capa); - FL_UNSET_EMBED(ary); ARY_SET_PTR(ary, ptr); ARY_SET_CAPA(ary, capa); ARY_SET_HEAP_LEN(ary, 0); @@ -932,7 +1002,7 @@ ary_make_shared(VALUE 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 shared = ary_alloc_heap(0); VALUE vshared = (VALUE)shared; rb_ary_transient_heap_evacuate(ary, TRUE); @@ -961,8 +1031,10 @@ ary_make_substitution(VALUE ary) { long len = RARRAY_LEN(ary); - if (len <= RARRAY_EMBED_LEN_MAX) { - VALUE subst = rb_ary_new2(len); + if (ary_embeddable_p(len)) { + VALUE subst = rb_ary_new_capa(len); + assert(ARY_EMBED_P(subst)); + ary_memcpy(subst, 0, len, RARRAY_CONST_PTR_TRANSIENT(ary)); ARY_SET_EMBED_LEN(subst, len); return subst; @@ -1023,6 +1095,30 @@ rb_ary_s_try_convert(VALUE dummy, VALUE ary) return rb_check_array_type(ary); } +/* :nodoc: */ +static VALUE +rb_ary_s_new(int argc, VALUE *argv, VALUE klass) +{ + VALUE ary; + + if (klass == rb_cArray) { + long size = 0; + if (argc > 0 && FIXNUM_P(argv[0])) { + size = FIX2LONG(argv[0]); + if (size < 0) size = 0; + } + + ary = ary_new(klass, size); + + rb_obj_call_init_kw(ary, argc, argv, RB_PASS_CALLED_KEYWORDS); + } + else { + ary = rb_class_new_instance_pass_kw(argc, argv, klass); + } + + return ary; +} + /* * call-seq: * Array.new -> new_empty_array @@ -1037,6 +1133,7 @@ rb_ary_s_try_convert(VALUE dummy, VALUE ary) * * With no block and a single \Array argument +array+, * returns a new \Array formed from +array+: + * * a = Array.new([:foo, 'bar', 2]) * a.class # => Array * a # => [:foo, "bar", 2] @@ -1044,12 +1141,14 @@ rb_ary_s_try_convert(VALUE dummy, VALUE ary) * With no block and a single \Integer argument +size+, * returns a new \Array of the given size * whose elements are all +nil+: + * * a = Array.new(3) * a # => [nil, nil, nil] * * With no block and arguments +size+ and +default_value+, * returns an \Array of the given size; * each element is that same +default_value+: + * * a = Array.new(3, 'x') * a # => ['x', 'x', 'x'] * @@ -1057,6 +1156,7 @@ rb_ary_s_try_convert(VALUE dummy, VALUE ary) * returns an \Array of the given size; * the block is called with each successive integer +index+; * the element for that +index+ is the return value from the block: + * * a = Array.new(3) {|index| "Element #{index}" } * a # => ["Element 0", "Element 1", "Element 2"] * @@ -1075,12 +1175,9 @@ 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); + 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"); } @@ -1181,15 +1278,15 @@ 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); + if (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, result = ary_alloc_heap(klass); + assert(!ARY_EMBED_P(result)); shared = ary_make_shared(ary); ARY_SET_PTR(result, RARRAY_CONST_PTR_TRANSIENT(ary)); @@ -1229,8 +1326,9 @@ ary_make_partial_step(VALUE ary, VALUE klass, long offset, long len, long step) long i; long j = offset + ((step > 0) ? 0 : (orig_len - 1)); + VALUE result = ary_new(klass, len); - if (len <= RARRAY_EMBED_LEN_MAX) { + if (ARY_EMBED_P(result)) { VALUE *ptr = (VALUE *)ARY_EMBED_PTR(result); for (i = 0; i < len; ++i) { RB_OBJ_WRITE(result, ptr+i, values[j]); @@ -1295,13 +1393,16 @@ ary_take_first_or_last(int argc, const VALUE *argv, VALUE ary, enum ary_take_pos * array << object -> self * * Appends +object+ to +self+; returns +self+: + * * a = [:foo, 'bar', 2] * a << :baz # => [:foo, "bar", 2, :baz] * * Appends +object+ as one element, even if it is another \Array: + * * a = [:foo, 'bar', 2] * a1 = a << [3, 4] * a1 # => [:foo, "bar", 2, [3, 4]] + * */ VALUE @@ -1334,15 +1435,17 @@ rb_ary_cat(VALUE ary, const VALUE *argv, long len) * Appends trailing elements. * * Appends each argument in +objects+ to +self+; returns +self+: + * * a = [:foo, 'bar', 2] * a.push(:baz, :bat) # => [:foo, "bar", 2, :baz, :bat] * * Appends each argument as one element, even if it is another \Array: + * * a = [:foo, 'bar', 2] * a1 = a.push([:baz, :bat], [:bam, :bad]) * a1 # => [:foo, "bar", 2, [:baz, :bat], [:bam, :bad]] * - * Array#append is an alias for \Array#push. + * Array#append is an alias for Array#push. * * Related: #pop, #shift, #unshift. */ @@ -1381,6 +1484,7 @@ rb_ary_pop(VALUE ary) * * When no argument is given and +self+ is not empty, * removes and returns the last element: + * * a = [:foo, 'bar', 2] * a.pop # => 2 * a # => [:foo, "bar"] @@ -1388,12 +1492,14 @@ rb_ary_pop(VALUE ary) * Returns +nil+ if the array is empty. * * When a non-negative \Integer argument +n+ is given and is in range, + * * removes and returns the last +n+ elements in a new \Array: * a = [:foo, 'bar', 2] * a.pop(2) # => ["bar", 2] * * If +n+ is positive and out of range, * removes and returns all elements: + * * a = [:foo, 'bar', 2] * a.pop(50) # => [:foo, "bar", 2] * @@ -1422,30 +1528,14 @@ rb_ary_shift(VALUE ary) VALUE top; long len = RARRAY_LEN(ary); - rb_ary_modify_check(ary); - if (len == 0) return Qnil; - top = RARRAY_AREF(ary, 0); - if (!ARY_SHARED_P(ary)) { - if (len < ARY_DEFAULT_SIZE) { - RARRAY_PTR_USE_TRANSIENT(ary, ptr, { - MEMMOVE(ptr, ptr+1, VALUE, len-1); - }); /* WB: no new reference */ - ARY_INCREASE_LEN(ary, -1); - ary_verify(ary); - return top; - } - assert(!ARY_EMBED_P(ary)); /* ARY_EMBED_LEN_MAX < ARY_DEFAULT_SIZE */ - - ARY_SET(ary, 0, Qnil); - ary_make_shared(ary); - } - else if (ARY_SHARED_ROOT_OCCUPIED(ARY_SHARED_ROOT(ary))) { - RARRAY_PTR_USE_TRANSIENT(ary, ptr, ptr[0] = Qnil); + if (len == 0) { + rb_ary_modify_check(ary); + return Qnil; } - ARY_INCREASE_PTR(ary, 1); /* shift ptr */ - ARY_INCREASE_LEN(ary, -1); - ary_verify(ary); + top = RARRAY_AREF(ary, 0); + + rb_ary_behead(ary, 1); return top; } @@ -1458,6 +1548,7 @@ rb_ary_shift(VALUE ary) * Removes and returns leading elements. * * When no argument is given, removes and returns the first element: + * * a = [:foo, 'bar', 2] * a.shift # => :foo * a # => ['bar', 2] @@ -1466,12 +1557,14 @@ rb_ary_shift(VALUE ary) * * When positive \Integer argument +n+ is given, removes the first +n+ elements; * returns those elements in a new \Array: + * * a = [:foo, 'bar', 2] * a.shift(2) # => [:foo, 'bar'] * a # => [2] * * If +n+ is as large as or larger than <tt>self.length</tt>, * removes all elements; returns those elements in a new \Array: + * * a = [:foo, 'bar', 2] * a.shift(3) # => [:foo, 'bar', 2] * @@ -1498,48 +1591,37 @@ rb_ary_shift_m(int argc, VALUE *argv, VALUE ary) return result; } -static VALUE -behead_shared(VALUE ary, long n) -{ - assert(ARY_SHARED_P(ary)); - rb_ary_modify_check(ary); - if (ARY_SHARED_ROOT_OCCUPIED(ARY_SHARED_ROOT(ary))) { - ary_mem_clear(ary, 0, n); - } - ARY_INCREASE_PTR(ary, n); - ARY_INCREASE_LEN(ary, -n); - ary_verify(ary); - return ary; -} - -static VALUE -behead_transient(VALUE ary, long n) -{ - rb_ary_modify_check(ary); - RARRAY_PTR_USE_TRANSIENT(ary, ptr, { - MEMMOVE(ptr, ptr+n, VALUE, RARRAY_LEN(ary)-n); - }); /* WB: no new reference */ - ARY_INCREASE_LEN(ary, -n); - ary_verify(ary); - return ary; -} - MJIT_FUNC_EXPORTED VALUE rb_ary_behead(VALUE ary, long n) { if (n <= 0) { return ary; } - else if (ARY_SHARED_P(ary)) { - return behead_shared(ary, n); - } - else if (RARRAY_LEN(ary) >= ARY_DEFAULT_SIZE) { + + rb_ary_modify_check(ary); + + if (!ARY_SHARED_P(ary)) { + if (ARY_EMBED_P(ary) || RARRAY_LEN(ary) < ARY_DEFAULT_SIZE) { + RARRAY_PTR_USE_TRANSIENT(ary, ptr, { + MEMMOVE(ptr, ptr + n, VALUE, RARRAY_LEN(ary) - n); + }); /* WB: no new reference */ + ARY_INCREASE_LEN(ary, -n); + ary_verify(ary); + return ary; + } + + ary_mem_clear(ary, 0, n); ary_make_shared(ary); - return behead_shared(ary, n); } - else { - return behead_transient(ary, n); + else if (ARY_SHARED_ROOT_OCCUPIED(ARY_SHARED_ROOT(ary))) { + ary_mem_clear(ary, 0, n); } + + ARY_INCREASE_PTR(ary, n); + ARY_INCREASE_LEN(ary, -n); + ary_verify(ary); + + return ary; } static VALUE @@ -1574,7 +1656,7 @@ ary_modify_for_unshift(VALUE ary, int argc) } /* 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 */ @@ -1632,6 +1714,7 @@ ary_ensure_room_for_unshift(VALUE ary, int argc) * array.unshift(*objects) -> self * * Prepends the given +objects+ to +self+: + * * a = [:foo, 'bar', 2] * a.unshift(:bam, :bat) # => [:bam, :bat, :foo, "bar", 2] * @@ -1725,12 +1808,14 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e); * Returns elements from +self+; does not modify +self+. * * When a single \Integer argument +index+ is given, returns the element at offset +index+: + * * a = [:foo, 'bar', 2] * a[0] # => :foo * a[2] # => 2 * a # => [:foo, "bar", 2] * * If +index+ is negative, counts relative to the end of +self+: + * * a = [:foo, 'bar', 2] * a[-1] # => 2 * a[-2] # => "bar" @@ -1739,12 +1824,14 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e); * * When two \Integer arguments +start+ and +length+ are given, * returns a new \Array of size +length+ containing successive elements beginning at offset +start+: + * * a = [:foo, 'bar', 2] * a[0, 2] # => [:foo, "bar"] * a[1, 2] # => ["bar", 2] * * If <tt>start + length</tt> is greater than <tt>self.length</tt>, * returns all elements from offset +start+ to the end: + * * a = [:foo, 'bar', 2] * a[0, 4] # => [:foo, "bar", 2] * a[1, 3] # => ["bar", 2] @@ -1758,6 +1845,7 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e); * When a single \Range argument +range+ is given, * treats <tt>range.min</tt> as +start+ above * and <tt>range.size</tt> as +length+ above: + * * a = [:foo, 'bar', 2] * a[0..1] # => [:foo, "bar"] * a[1..2] # => ["bar", 2] @@ -1765,31 +1853,36 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e); * Special case: If <tt>range.start == a.size</tt>, returns a new empty \Array. * * If <tt>range.end</tt> is negative, calculates the end index from the end: + * * a = [:foo, 'bar', 2] * a[0..-1] # => [:foo, "bar", 2] * a[0..-2] # => [:foo, "bar"] * a[0..-3] # => [:foo] * * If <tt>range.start</tt> is negative, calculates the start index from the end: + * * a = [:foo, 'bar', 2] * a[-1..2] # => [2] * a[-2..2] # => ["bar", 2] * a[-3..2] # => [:foo, "bar", 2] * * If <tt>range.start</tt> is larger than the array size, returns +nil+. + * * a = [:foo, 'bar', 2] * a[4..1] # => nil * a[4..0] # => nil * a[4..-1] # => nil * * When a single Enumerator::ArithmeticSequence argument +aseq+ is given, - * returns an Array of elements corresponding to the indexes produced by + * returns an \Array of elements corresponding to the indexes produced by * the sequence. + * * a = ['--', 'data1', '--', 'data2', '--', 'data3'] * a[(1..).step(2)] # => ["data1", "data2", "data3"] * * Unlike slicing with range, if the start or the end of the arithmetic sequence * is larger than array size, throws RangeError. + * * a = ['--', 'data1', '--', 'data2', '--', 'data3'] * a[(1..11).step(2)] * # RangeError (((1..11).step(2)) out of range) @@ -1798,6 +1891,7 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e); * * If given a single argument, and its type is not one of the listed, tries to * convert it to Integer, and raises if it is impossible: + * * a = [:foo, 'bar', 2] * # Raises TypeError (no implicit conversion of Symbol into Integer): * a[:foo] @@ -1856,6 +1950,7 @@ rb_ary_aref1(VALUE ary, VALUE arg) * a = [:foo, 'bar', 2] * a.at(0) # => :foo * a.at(2) # => 2 + * */ VALUE @@ -1872,6 +1967,7 @@ rb_ary_at(VALUE ary, VALUE pos) * Returns elements from +self+; does not modify +self+. * * When no argument is given, returns the first element: + * * a = [:foo, 'bar', 2] * a.first # => :foo * a # => [:foo, "bar", 2] @@ -1880,14 +1976,17 @@ rb_ary_at(VALUE ary, VALUE pos) * * When non-negative \Integer argument +n+ is given, * returns the first +n+ elements in a new \Array: + * * a = [:foo, 'bar', 2] * a.first(2) # => [:foo, "bar"] * * If <tt>n >= array.size</tt>, returns all elements: + * * a = [:foo, 'bar', 2] * a.first(50) # => [:foo, "bar", 2] * * If <tt>n == 0</tt> returns an new empty \Array: + * * a = [:foo, 'bar', 2] * a.first(0) # [] * @@ -1913,6 +2012,7 @@ rb_ary_first(int argc, VALUE *argv, VALUE ary) * Returns elements from +self+; +self+ is not modified. * * When no argument is given, returns the last element: + * * a = [:foo, 'bar', 2] * a.last # => 2 * a # => [:foo, "bar", 2] @@ -1921,14 +2021,17 @@ rb_ary_first(int argc, VALUE *argv, VALUE ary) * * When non-negative \Innteger argument +n+ is given, * returns the last +n+ elements in a new \Array: + * * a = [:foo, 'bar', 2] * a.last(2) # => ["bar", 2] * * If <tt>n >= array.size</tt>, returns all elements: + * * a = [:foo, 'bar', 2] * a.last(50) # => [:foo, "bar", 2] * * If <tt>n == 0</tt>, returns an new empty \Array: + * * a = [:foo, 'bar', 2] * a.last(0) # [] * @@ -1958,10 +2061,12 @@ rb_ary_last(int argc, const VALUE *argv, VALUE ary) * * With the single \Integer argument +index+, * returns the element at offset +index+: + * * a = [:foo, 'bar', 2] * a.fetch(1) # => "bar" * * If +index+ is negative, counts from the end of the array: + * * a = [:foo, 'bar', 2] * a.fetch(-1) # => 2 * a.fetch(-2) # => "bar" @@ -1969,6 +2074,7 @@ rb_ary_last(int argc, const VALUE *argv, VALUE ary) * With arguments +index+ and +default_value+, * returns the element at offset +index+ if index is in range, * otherwise returns +default_value+: + * * a = [:foo, 'bar', 2] * a.fetch(1, nil) # => "bar" * @@ -1979,6 +2085,7 @@ rb_ary_last(int argc, const VALUE *argv, VALUE ary) * a = [:foo, 'bar', 2] * a.fetch(1) {|index| raise 'Cannot happen' } # => "bar" * a.fetch(50) {|index| "Value for #{index}" } # => "Value for 50" + * */ static VALUE @@ -2020,6 +2127,7 @@ rb_ary_fetch(int argc, VALUE *argv, VALUE ary) * When argument +object+ is given but no block, * returns the index of the first element +element+ * for which <tt>object == element</tt>: + * * a = [:foo, 'bar', 2, 'bar'] * a.index('bar') # => 1 * @@ -2028,12 +2136,14 @@ rb_ary_fetch(int argc, VALUE *argv, VALUE ary) * When both argument +object+ and a block are given, * calls the block with each successive element; * returns the index of the first element for which the block returns a truthy value: + * * a = [:foo, 'bar', 2, 'bar'] * a.index {|element| element == 'bar' } # => 1 * * Returns +nil+ if the block never returns a truthy value. * * When neither an argument nor a block is given, returns a new Enumerator: + * * a = [:foo, 'bar', 2] * e = a.index * e # => #<Enumerator: [:foo, "bar", 2]:index> @@ -2081,6 +2191,7 @@ rb_ary_index(int argc, VALUE *argv, VALUE ary) * Returns the index of the last element for which <tt>object == element</tt>. * * When argument +object+ is given but no block, returns the index of the last such element found: + * * a = [:foo, 'bar', 2, 'bar'] * a.rindex('bar') # => 3 * @@ -2088,6 +2199,7 @@ rb_ary_index(int argc, VALUE *argv, VALUE ary) * * When a block is given but no argument, calls the block with each successive element; * returns the index of the last element for which the block returns a truthy value: + * * a = [:foo, 'bar', 2, 'bar'] * a.rindex {|element| element == 'bar' } # => 3 * @@ -2251,12 +2363,18 @@ rb_ary_resize(VALUE ary, long 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) { @@ -2296,16 +2414,19 @@ ary_aset_by_rb_ary_splice(VALUE ary, long beg, long len, VALUE val) * When \Integer argument +index+ is given, assigns +object+ to an element in +self+. * * If +index+ is non-negative, assigns +object+ the element at offset +index+: + * * a = [:foo, 'bar', 2] * a[0] = 'foo' # => "foo" * a # => ["foo", "bar", 2] * * If +index+ is greater than <tt>self.length</tt>, extends the array: + * * a = [:foo, 'bar', 2] * a[7] = 'foo' # => "foo" * a # => [:foo, "bar", 2, nil, nil, nil, nil, "foo"] * * If +index+ is negative, counts backwards from the end of the array: + * * a = [:foo, 'bar', 2] * a[-1] = 'two' # => "two" * a # => [:foo, "bar", "two"] @@ -2313,11 +2434,13 @@ ary_aset_by_rb_ary_splice(VALUE ary, long beg, long len, VALUE val) * When \Integer arguments +start+ and +length+ are given and +object+ is not an \Array, * removes <tt>length - 1</tt> elements beginning at offset +start+, * and assigns +object+ at offset +start+: + * * a = [:foo, 'bar', 2] * a[0, 2] = 'foo' # => "foo" * a # => ["foo", 2] * * If +start+ is negative, counts backwards from the end of the array: + * * a = [:foo, 'bar', 2] * a[-2, 2] = 'foo' # => "foo" * a # => [:foo, "foo"] @@ -2325,17 +2448,20 @@ ary_aset_by_rb_ary_splice(VALUE ary, long beg, long len, VALUE val) * If +start+ is non-negative and outside the array (<tt> >= self.size</tt>), * extends the array with +nil+, assigns +object+ at offset +start+, * and ignores +length+: + * * a = [:foo, 'bar', 2] * a[6, 50] = 'foo' # => "foo" * a # => [:foo, "bar", 2, nil, nil, nil, "foo"] * * If +length+ is zero, shifts elements at and following offset +start+ * and assigns +object+ at offset +start+: + * * a = [:foo, 'bar', 2] * a[1, 0] = 'foo' # => "foo" * a # => [:foo, "foo", "bar", 2] * * If +length+ is too large for the existing array, does not extend the array: + * * a = [:foo, 'bar', 2] * a[1, 5] = 'foo' # => "foo" * a # => [:foo, "foo"] @@ -2343,29 +2469,34 @@ ary_aset_by_rb_ary_splice(VALUE ary, long beg, long len, VALUE val) * When \Range argument +range+ is given and +object+ is an \Array, * removes <tt>length - 1</tt> elements beginning at offset +start+, * and assigns +object+ at offset +start+: + * * a = [:foo, 'bar', 2] * a[0..1] = 'foo' # => "foo" * a # => ["foo", 2] * * if <tt>range.begin</tt> is negative, counts backwards from the end of the array: + * * a = [:foo, 'bar', 2] * a[-2..2] = 'foo' # => "foo" * a # => [:foo, "foo"] * * If the array length is less than <tt>range.begin</tt>, * assigns +object+ at offset <tt>range.begin</tt>, and ignores +length+: + * * a = [:foo, 'bar', 2] * a[6..50] = 'foo' # => "foo" * a # => [:foo, "bar", 2, nil, nil, nil, "foo"] * * If <tt>range.end</tt> is zero, shifts elements at and following offset +start+ * and assigns +object+ at offset +start+: + * * a = [:foo, 'bar', 2] * a[1..0] = 'foo' # => "foo" * a # => [:foo, "foo", "bar", 2] * * If <tt>range.end</tt> is negative, assigns +object+ at offset +start+, * retains <tt>range.end.abs -1</tt> elements past that, and removes those beyond: + * * a = [:foo, 'bar', 2] * a[1..-1] = 'foo' # => "foo" * a # => [:foo, "foo"] @@ -2379,9 +2510,11 @@ ary_aset_by_rb_ary_splice(VALUE ary, long beg, long len, VALUE val) * * If <tt>range.end</tt> is too large for the existing array, * replaces array elements, but does not extend the array with +nil+ values: + * * a = [:foo, 'bar', 2] * a[1..5] = 'foo' # => "foo" * a # => [:foo, "foo"] + * */ static VALUE @@ -2418,15 +2551,18 @@ rb_ary_aset(int argc, VALUE *argv, VALUE ary) * * When +index+ is non-negative, inserts all given +objects+ * before the element at offset +index+: + * * a = [:foo, 'bar', 2] * a.insert(1, :bat, :bam) # => [:foo, :bat, :bam, "bar", 2] * * Extends the array if +index+ is beyond the array (<tt>index >= self.size</tt>): + * * a = [:foo, 'bar', 2] * a.insert(5, :bat, :bam) * a # => [:foo, "bar", 2, nil, nil, :bat, :bam] * * Does nothing if no objects given: + * * a = [:foo, 'bar', 2] * a.insert(1) * a.insert(50) @@ -2435,9 +2571,11 @@ rb_ary_aset(int argc, VALUE *argv, VALUE ary) * * When +index+ is negative, inserts all given +objects+ * _after_ the element at offset <tt>index+self.size</tt>: + * * a = [:foo, 'bar', 2] * a.insert(-2, :bat, :bam) * a # => [:foo, "bar", :bat, :bam, 2] + * */ static VALUE @@ -2482,29 +2620,35 @@ ary_enum_length(VALUE ary, VALUE args, VALUE eobj) * * When a block given, passes each successive array element to the block; * returns +self+: + * * a = [:foo, 'bar', 2] * a.each {|element| puts "#{element.class} #{element}" } * * Output: + * * Symbol foo * String bar * Integer 2 * * Allows the array to be modified during iteration: + * * a = [:foo, 'bar', 2] * a.each {|element| puts element; a.clear if element.to_s.start_with?('b') } * * Output: + * * foo * bar * * When no block given, returns a new \Enumerator: * a = [:foo, 'bar', 2] + * * e = a.each * e # => #<Enumerator: [:foo, "bar", 2]:each> * a1 = e.each {|element| puts "#{element.class} #{element}" } * * Output: + * * Symbol foo * String bar * Integer 2 @@ -2533,29 +2677,35 @@ rb_ary_each(VALUE ary) * * When a block given, passes each successive array index to the block; * returns +self+: + * * a = [:foo, 'bar', 2] * a.each_index {|index| puts "#{index} #{a[index]}" } * * Output: + * * 0 foo * 1 bar * 2 2 * * Allows the array to be modified during iteration: + * * a = [:foo, 'bar', 2] * a.each_index {|index| puts index; a.clear if index > 0 } * * Output: + * * 0 * 1 * * When no block given, returns a new \Enumerator: + * * a = [:foo, 'bar', 2] * e = a.each_index * e # => #<Enumerator: [:foo, "bar", 2]:each_index> * a1 = e.each {|index| puts "#{index} #{a[index]}"} * * Output: + * * 0 foo * 1 bar * 2 2 @@ -2584,28 +2734,35 @@ rb_ary_each_index(VALUE ary) * * When a block given, passes, in reverse order, each element to the block; * returns +self+: + * * a = [:foo, 'bar', 2] * a.reverse_each {|element| puts "#{element.class} #{element}" } * * Output: + * * Integer 2 * String bar * Symbol foo * * Allows the array to be modified during iteration: + * * a = [:foo, 'bar', 2] * a.reverse_each {|element| puts element; a.clear if element.to_s.start_with?('b') } * * Output: + * * 2 * bar * * When no block given, returns a new \Enumerator: + * * a = [:foo, 'bar', 2] * e = a.reverse_each * e # => #<Enumerator: [:foo, "bar", 2]:reverse_each> * a1 = e.each {|element| puts "#{element.class} #{element}" } + * * Output: + * * Integer 2 * String bar * Symbol foo @@ -2818,22 +2975,27 @@ rb_ary_join(VALUE ary, VALUE sep) * array.join(separator = $,) -> new_string * * Returns the new \String formed by joining the array elements after conversion. - * For each element +element+ + * For each element +element+: + * * - Uses <tt>element.to_s</tt> if +element+ is not a <tt>kind_of?(Array)</tt>. * - Uses recursive <tt>element.join(separator)</tt> if +element+ is a <tt>kind_of?(Array)</tt>. * * With no argument, joins using the output field separator, <tt>$,</tt>: + * * a = [:foo, 'bar', 2] * $, # => nil * a.join # => "foobar2" * * With \string argument +separator+, joins using that separator: + * * a = [:foo, 'bar', 2] * a.join("\n") # => "foo\nbar\n2" * * Joins recursively for nested Arrays: + * * a = [:foo, [:bar, [:baz, :bat]]] * a.join # => "foobarbazbat" + * */ static VALUE rb_ary_join_m(int argc, VALUE *argv, VALUE ary) @@ -2874,6 +3036,7 @@ inspect_ary(VALUE ary, VALUE dummy, int recur) * * Returns the new \String formed by calling method <tt>#inspect</tt> * on each array element: + * * a = [:foo, 'bar', 2] * a.inspect # => "[:foo, \"bar\", 2]" * @@ -2898,10 +3061,12 @@ rb_ary_to_s(VALUE ary) * to_a -> self or new_array * * When +self+ is an instance of \Array, returns +self+: + * * a = [:foo, 'bar', 2] * a.to_a # => [:foo, "bar", 2] * * Otherwise, returns a new \Array containing the elements of +self+: + * * class MyArray < Array; end * a = MyArray.new(['foo', 'bar', 'two']) * a.instance_of?(Array) # => false @@ -2909,6 +3074,7 @@ rb_ary_to_s(VALUE ary) * a1 = a.to_a * a1 # => ["foo", "bar", "two"] * a1.class # => Array # Not MyArray + * */ static VALUE @@ -2932,16 +3098,19 @@ rb_ary_to_a(VALUE ary) * When a block is given, calls the block with each array element; * the block must return a 2-element \Array whose two elements * form a key-value pair in the returned \Hash: + * * a = ['foo', :bar, 1, [2, 3], {baz: 4}] * h = a.to_h {|item| [item, item] } * h # => {"foo"=>"foo", :bar=>:bar, 1=>1, [2, 3]=>[2, 3], {:baz=>4}=>{:baz=>4}} * * When no block is given, +self+ must be an \Array of 2-element sub-arrays, * each sub-array is formed into a key-value pair in the new \Hash: + * * [].to_h # => {} * a = [['foo', 'zero'], ['bar', 'one'], ['baz', 'two']] * h = a.to_h * h # => {"foo"=>"zero", "bar"=>"one", "baz"=>"two"} + * */ static VALUE @@ -3012,8 +3181,10 @@ rb_ary_reverse(VALUE ary) * array.reverse! -> self * * Reverses +self+ in place: + * * a = ['foo', 'bar', 'two'] * a.reverse! # => ["two", "bar", "foo"] + * */ static VALUE @@ -3026,10 +3197,12 @@ rb_ary_reverse_bang(VALUE ary) * call-seq: * array.reverse -> new_array * - * Returns a new \Array with the elements of +self+ in reverse order. + * Returns a new \Array with the elements of +self+ in reverse order: + * * a = ['foo', 'bar', 'two'] * a1 = a.reverse * a1 # => ["two", "bar", "foo"] + * */ static VALUE @@ -3097,35 +3270,42 @@ rb_ary_rotate(VALUE ary, long cnt) * Rotates +self+ in place by moving elements from one end to the other; returns +self+. * * When no argument given, rotates the first element to the last position: + * * a = [:foo, 'bar', 2, 'bar'] * a.rotate! # => ["bar", 2, "bar", :foo] * * When given a non-negative \Integer +count+, * rotates +count+ elements from the beginning to the end: + * * a = [:foo, 'bar', 2] * a.rotate!(2) * a # => [2, :foo, "bar"] * * If +count+ is large, uses <tt>count % array.size</tt> as the count: + * * a = [:foo, 'bar', 2] * a.rotate!(20) * a # => [2, :foo, "bar"] * * If +count+ is zero, returns +self+ unmodified: + * * a = [:foo, 'bar', 2] * a.rotate!(0) * a # => [:foo, "bar", 2] * * When given a negative Integer +count+, rotates in the opposite direction, * from end to beginning: + * * a = [:foo, 'bar', 2] * a.rotate!(-2) * a # => ["bar", 2, :foo] * * If +count+ is small (far from zero), uses <tt>count % array.size</tt> as the count: + * * a = [:foo, 'bar', 2] * a.rotate!(-5) * a # => ["bar", 2, :foo] + * */ static VALUE @@ -3146,36 +3326,43 @@ rb_ary_rotate_bang(int argc, VALUE *argv, VALUE ary) * * When no argument given, returns a new \Array that is like +self+, * except that the first element has been rotated to the last position: + * * a = [:foo, 'bar', 2, 'bar'] * a1 = a.rotate * a1 # => ["bar", 2, "bar", :foo] * * When given a non-negative \Integer +count+, * returns a new \Array with +count+ elements rotated from the beginning to the end: + * * a = [:foo, 'bar', 2] * a1 = a.rotate(2) * a1 # => [2, :foo, "bar"] * * If +count+ is large, uses <tt>count % array.size</tt> as the count: + * * a = [:foo, 'bar', 2] * a1 = a.rotate(20) * a1 # => [2, :foo, "bar"] * * If +count+ is zero, returns a copy of +self+, unmodified: + * * a = [:foo, 'bar', 2] * a1 = a.rotate(0) * a1 # => [:foo, "bar", 2] * * When given a negative \Integer +count+, rotates in the opposite direction, * from end to beginning: + * * a = [:foo, 'bar', 2] * a1 = a.rotate(-2) * a1 # => ["bar", 2, :foo] * * If +count+ is small (far from zero), uses <tt>count % array.size</tt> as the count: + * * a = [:foo, 'bar', 2] * a1 = a.rotate(-5) * a1 # => ["bar", 2, :foo] + * */ static VALUE @@ -3276,6 +3463,7 @@ sort_2(const void *ap, const void *bp, void *dummy) * * With no block, compares elements using operator <tt><=></tt> * (see Comparable): + * * a = 'abcde'.split('').shuffle * a # => ["e", "b", "d", "a", "c"] * a.sort! @@ -3283,11 +3471,13 @@ sort_2(const void *ap, const void *bp, void *dummy) * * With a block, calls the block with each element pair; * for each element pair +a+ and +b+, the block should return an integer: + * * - Negative when +b+ is to follow +a+. * - Zero when +a+ and +b+ are equivalent. * - Positive when +a+ is to follow +b+. * * Example: + * * a = 'abcde'.split('').shuffle * a # => ["e", "b", "d", "a", "c"] * a.sort! {|a, b| a <=> b } @@ -3297,10 +3487,12 @@ sort_2(const void *ap, const void *bp, void *dummy) * * When the block returns zero, the order for +a+ and +b+ is indeterminate, * and may be unstable: + * * a = 'abcde'.split('').shuffle * a # => ["e", "b", "d", "a", "c"] * a.sort! {|a, b| 0 } * a # => ["d", "e", "c", "a", "b"] + * */ VALUE @@ -3373,6 +3565,7 @@ rb_ary_sort_bang(VALUE ary) * * With no block, compares elements using operator <tt><=></tt> * (see Comparable): + * * a = 'abcde'.split('').shuffle * a # => ["e", "b", "d", "a", "c"] * a1 = a.sort @@ -3380,11 +3573,13 @@ rb_ary_sort_bang(VALUE ary) * * With a block, calls the block with each element pair; * for each element pair +a+ and +b+, the block should return an integer: + * * - Negative when +b+ is to follow +a+. * - Zero when +a+ and +b+ are equivalent. * - Positive when +a+ is to follow +b+. * * Example: + * * a = 'abcde'.split('').shuffle * a # => ["e", "b", "d", "a", "c"] * a1 = a.sort {|a, b| a <=> b } @@ -3394,6 +3589,7 @@ rb_ary_sort_bang(VALUE ary) * * When the block returns zero, the order for +a+ and +b+ is indeterminate, * and may be unstable: + * * a = 'abcde'.split('').shuffle * a # => ["e", "b", "d", "a", "c"] * a1 = a.sort {|a, b| 0 } @@ -3510,6 +3706,7 @@ sort_by_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, dummy)) * For duplicates returned by the block, the ordering is indeterminate, and may be unstable. * * This example sorts strings based on their sizes: + * * a = ['aaaa', 'bbb', 'cc', 'd'] * a.sort_by! {|element| element.size } * a # => ["d", "cc", "bbb", "aaaa"] @@ -3518,6 +3715,7 @@ sort_by_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, dummy)) * * a = ['aaaa', 'bbb', 'cc', 'd'] * a.sort_by! # => #<Enumerator: ["aaaa", "bbb", "cc", "d"]:sort_by!> + * */ static VALUE @@ -3540,6 +3738,7 @@ rb_ary_sort_by_bang(VALUE ary) * * Calls the block, if given, with each element of +self+; * returns a new \Array whose elements are the return values from the block: + * * a = [:foo, 'bar', 2] * a1 = a.map {|element| element.class } * a1 # => [Symbol, String, Integer] @@ -3574,10 +3773,12 @@ rb_ary_collect(VALUE ary) * * Calls the block, if given, with each element; * replaces the element with the block's return value: + * * a = [:foo, 'bar', 2] * a.map! { |element| element.class } # => [Symbol, String, Integer] * * Returns a new \Enumerator if no block given: + * * a = [:foo, 'bar', 2] * a1 = a.map! * a1 # => #<Enumerator: [:foo, "bar", 2]:map!> @@ -3660,32 +3861,39 @@ append_values_at_single(VALUE result, VALUE ary, long olen, VALUE idx) * of +self+ at the given \Integer or \Range +indexes+. * * For each positive +index+, returns the element at offset +index+: + * * a = [:foo, 'bar', 2] * a.values_at(0, 2) # => [:foo, 2] * a.values_at(0..1) # => [:foo, "bar"] * * The given +indexes+ may be in any order, and may repeat: + * * a = [:foo, 'bar', 2] * a.values_at(2, 0, 1, 0, 2) # => [2, :foo, "bar", :foo, 2] * a.values_at(1, 0..2) # => ["bar", :foo, "bar", 2] * * Assigns +nil+ for an +index+ that is too large: + * * a = [:foo, 'bar', 2] * a.values_at(0, 3, 1, 3) # => [:foo, nil, "bar", nil] * * Returns a new empty \Array if no arguments given. * * For each negative +index+, counts backward from the end of the array: + * * a = [:foo, 'bar', 2] * a.values_at(-1, -3) # => [2, :foo] * * Assigns +nil+ for an +index+ that is too small: + * * a = [:foo, 'bar', 2] * a.values_at(0, -5, 1, -6, 2) # => [:foo, nil, "bar", nil, 2] * * The given +indexes+ may have a mixture of signs: + * * a = [:foo, 'bar', 2] * a.values_at(0, -2, 1, -1) # => [:foo, "bar", "bar", 2] + * */ static VALUE @@ -3709,11 +3917,13 @@ rb_ary_values_at(int argc, VALUE *argv, VALUE ary) * Calls the block, if given, with each element of +self+; * returns a new \Array containing those elements of +self+ * for which the block returns a truthy value: + * * a = [:foo, 'bar', 2, :bam] * a1 = a.select {|element| element.to_s.start_with?('b') } * a1 # => ["bar", :bam] * * Returns a new \Enumerator if no block given: + * * a = [:foo, 'bar', 2, :bam] * a.select # => #<Enumerator: [:foo, "bar", 2, :bam]:select> * @@ -3790,12 +4000,14 @@ select_bang_ensure(VALUE a) * removes from +self+ those elements for which the block returns +false+ or +nil+. * * Returns +self+ if any elements were removed: + * * a = [:foo, 'bar', 2, :bam] * a.select! {|element| element.to_s.start_with?('b') } # => ["bar", :bam] * * Returns +nil+ if no elements were removed. * * Returns a new \Enumerator if no block given: + * * a = [:foo, 'bar', 2, :bam] * a.select! # => #<Enumerator: [:foo, "bar", 2, :bam]:select!> * @@ -3822,12 +4034,15 @@ rb_ary_select_bang(VALUE ary) * * Retains those elements for which the block returns a truthy value; * deletes all other elements; returns +self+: + * * a = [:foo, 'bar', 2, :bam] * a.keep_if {|element| element.to_s.start_with?('b') } # => ["bar", :bam] * * Returns a new \Enumerator if no block given: + * * a = [:foo, 'bar', 2, :bam] * a.keep_if # => #<Enumerator: [:foo, "bar", 2, :bam]:keep_if> + * */ static VALUE @@ -3856,11 +4071,12 @@ ary_resize_smaller(VALUE ary, long len) * array.delete(obj) -> deleted_object * array.delete(obj) {|nosuch| ... } -> deleted_object or block_return * - * Removes zero or more elements from +self+; returns +self+. + * Removes zero or more elements from +self+. * * When no block is given, * removes from +self+ each element +ele+ such that <tt>ele == obj</tt>; * returns the last deleted element: + * * s1 = 'bar'; s2 = 'bar' * a = [:foo, s1, 2, s2] * a.delete('bar') # => "bar" @@ -3873,14 +4089,17 @@ ary_resize_smaller(VALUE ary, long len) * * If any such elements are found, ignores the block * and returns the last deleted element: + * * s1 = 'bar'; s2 = 'bar' * a = [:foo, s1, 2, s2] * deleted_obj = a.delete('bar') {|obj| fail 'Cannot happen' } * a # => [:foo, 2] * * If no such elements are found, returns the block's return value: + * * a = [:foo, 'bar', 2] * a.delete(:nosuch) {|obj| "#{obj} not found" } # => "nosuch not found" + * */ VALUE @@ -3966,6 +4185,7 @@ rb_ary_delete_at(VALUE ary, long pos) * Deletes an element from +self+, per the given \Integer +index+. * * When +index+ is non-negative, deletes the element at offset +index+: + * * a = [:foo, 'bar', 2] * a.delete_at(1) # => "bar" * a # => [:foo, 2] @@ -3973,6 +4193,7 @@ rb_ary_delete_at(VALUE ary, long pos) * If index is too large, returns +nil+. * * When +index+ is negative, counts backward from the end of the array: + * * a = [:foo, 'bar', 2] * a.delete_at(-2) # => "bar" * a # => [:foo, 2] @@ -4026,11 +4247,13 @@ ary_slice_bang_by_rb_ary_splice(VALUE ary, long pos, long len) * * When the only argument is an \Integer +n+, * removes and returns the _nth_ element in +self+: + * * a = [:foo, 'bar', 2] * a.slice!(1) # => "bar" * a # => [:foo, 2] * * If +n+ is negative, counts backwards from the end of +self+: + * * a = [:foo, 'bar', 2] * a.slice!(-1) # => 2 * a # => [:foo, "bar"] @@ -4039,13 +4262,15 @@ ary_slice_bang_by_rb_ary_splice(VALUE ary, long pos, long len) * * When the only arguments are Integers +start+ and +length+, * removes +length+ elements from +self+ beginning at offset +start+; - * returns the deleted objects in a new Array: + * returns the deleted objects in a new \Array: + * * a = [:foo, 'bar', 2] * a.slice!(0, 2) # => [:foo, "bar"] * a # => [2] * * If <tt>start + length</tt> exceeds the array size, * removes and returns all elements from offset +start+ to the end: + * * a = [:foo, 'bar', 2] * a.slice!(1, 50) # => ["bar", 2] * a # => [:foo] @@ -4057,8 +4282,9 @@ ary_slice_bang_by_rb_ary_splice(VALUE ary, long pos, long len) * * When the only argument is a \Range object +range+, * treats <tt>range.min</tt> as +start+ above and <tt>range.size</tt> as +length+ above: + * * a = [:foo, 'bar', 2] - * a.slice!(1..2) # => ["bar", 2] + * a.slice!(1..2) # => ["bar", 2] * a # => [:foo] * * If <tt>range.start == a.size</tt>, returns a new empty \Array. @@ -4066,15 +4292,18 @@ ary_slice_bang_by_rb_ary_splice(VALUE ary, long pos, long len) * If <tt>range.start</tt> is larger than the array size, returns +nil+. * * If <tt>range.end</tt> is negative, counts backwards from the end of the array: + * * a = [:foo, 'bar', 2] * a.slice!(0..-2) # => [:foo, "bar"] * a # => [2] * * If <tt>range.start</tt> is negative, * calculates the start index backwards from the end of the array: + * * a = [:foo, 'bar', 2] * a.slice!(-2..2) # => ["bar", 2] * a # => [:foo] + * */ static VALUE @@ -4161,14 +4390,17 @@ ary_reject_bang(VALUE ary) * Removes each element for which the block returns a truthy value. * * Returns +self+ if any elements removed: + * * a = [:foo, 'bar', 2, 'bat'] * a.reject! {|element| element.to_s.start_with?('b') } # => [:foo, 2] * * Returns +nil+ if no elements removed. * * Returns a new \Enumerator if no block given: + * * a = [:foo, 'bar', 2] * a.reject! # => #<Enumerator: [:foo, "bar", 2]:reject!> + * */ static VALUE @@ -4186,13 +4418,16 @@ rb_ary_reject_bang(VALUE ary) * * Returns a new \Array whose elements are all those from +self+ * for which the block returns +false+ or +nil+: + * * a = [:foo, 'bar', 2, 'bat'] * a1 = a.reject {|element| element.to_s.start_with?('b') } * a1 # => [:foo, 2] * * Returns a new \Enumerator if no block given: + * * a = [:foo, 'bar', 2] * a.reject # => #<Enumerator: [:foo, "bar", 2]:reject> + * */ static VALUE @@ -4213,13 +4448,16 @@ rb_ary_reject(VALUE ary) * * Removes each element in +self+ for which the block returns a truthy value; * returns +self+: + * * a = [:foo, 'bar', 2, 'bat'] * a.delete_if {|element| element.to_s.start_with?('b') } # => [:foo, 2] * * Returns a new \Enumerator if no block given: + * * a = [:foo, 'bar', 2] * a.delete_if # => #<Enumerator: [:foo, "bar", 2]:delete_if> - */ + * +3 */ static VALUE rb_ary_delete_if(VALUE ary) @@ -4267,10 +4505,12 @@ take_items(VALUE obj, long n) * * Each nested array <tt>new_array[n]</tt> is of size <tt>other_arrays.size+1</tt>, * and contains: + * * - The _nth_ element of +self+. * - The _nth_ element of each of the +other_arrays+. * * If all +other_arrays+ and +self+ are the same size: + * * a = [:a0, :a1, :a2, :a3] * b = [:b0, :b1, :b2, :b3] * c = [:c0, :c1, :c2, :c3] @@ -4279,6 +4519,7 @@ take_items(VALUE obj, long n) * * If any array in +other_arrays+ is smaller than +self+, * fills to <tt>self.size</tt> with +nil+: + * * a = [:a0, :a1, :a2, :a3] * b = [:b0, :b1, :b2] * c = [:c0, :c1] @@ -4287,23 +4528,27 @@ take_items(VALUE obj, long n) * * If any array in +other_arrays+ is larger than +self+, * its trailing elements are ignored: + * * a = [:a0, :a1, :a2, :a3] * b = [:b0, :b1, :b2, :b3, :b4] * c = [:c0, :c1, :c2, :c3, :c4, :c5] * d = a.zip(b, c) * d # => [[:a0, :b0, :c0], [:a1, :b1, :c1], [:a2, :b2, :c2], [:a3, :b3, :c3]] * - * When a block is given, calls the block with each of the sub-arrays (formed as above); returns nil + * When a block is given, calls the block with each of the sub-arrays (formed as above); returns +nil+: + * * a = [:a0, :a1, :a2, :a3] * b = [:b0, :b1, :b2, :b3] * c = [:c0, :c1, :c2, :c3] * a.zip(b, c) {|sub_array| p sub_array} # => nil * * Output: + * * [:a0, :b0, :c0] * [:a1, :b1, :c1] * [:a2, :b2, :c2] * [:a3, :b3, :c3] + * */ static VALUE @@ -4370,8 +4615,10 @@ rb_ary_zip(int argc, VALUE *argv, VALUE ary) * * Transposes the rows and columns in an \Array of Arrays; * the nested Arrays must all be the same size: + * * a = [[:a0, :a1], [:b0, :b1], [:c0, :c1]] * a.transpose # => [[:a0, :b0, :c0], [:a1, :b1, :c1]] + * */ static VALUE @@ -4407,8 +4654,10 @@ rb_ary_transpose(VALUE ary) * array.replace(other_array) -> self * * Replaces the content of +self+ with the content of +other_array+; returns +self+: + * * a = [:foo, 'bar', 2] * a.replace(['foo', :bar, 3]) # => ["foo", :bar, 3] + * */ VALUE @@ -4418,31 +4667,35 @@ rb_ary_replace(VALUE copy, VALUE orig) orig = to_ary(orig); if (copy == orig) return copy; - if (RARRAY_LEN(orig) <= RARRAY_EMBED_LEN_MAX) { - VALUE shared_root = 0; + rb_ary_reset(copy); - if (ARY_OWNS_HEAP_P(copy)) { - ary_heap_free(copy); - } - else if (ARY_SHARED_P(copy)) { - shared_root = ARY_SHARED_ROOT(copy); - FL_UNSET_SHARED(copy); - } - FL_SET_EMBED(copy); + /* orig has enough space to embed the contents of orig. */ + if (RARRAY_LEN(orig) <= ary_embed_capa(copy)) { + assert(ARY_EMBED_P(copy)); ary_memcpy(copy, 0, RARRAY_LEN(orig), RARRAY_CONST_PTR_TRANSIENT(orig)); - if (shared_root) { - rb_ary_decrement_share(shared_root); - } - ARY_SET_LEN(copy, RARRAY_LEN(orig)); + ARY_SET_EMBED_LEN(copy, RARRAY_LEN(orig)); } +#if USE_RVARGC + /* orig is embedded but copy does not have enough space to embed the + * contents of orig. */ + else if (ARY_EMBED_P(orig)) { + long len = ARY_EMBED_LEN(orig); + VALUE *ptr = ary_heap_alloc(copy, len); + + FL_UNSET_EMBED(copy); + ARY_SET_PTR(copy, ptr); + ARY_SET_LEN(copy, len); + ARY_SET_CAPA(copy, len); + + // No allocation and exception expected that could leave `copy` in a + // bad state from the edits above. + ary_memcpy(copy, 0, len, RARRAY_CONST_PTR_TRANSIENT(orig)); + } +#endif + /* Otherwise, orig is on heap and copy does not have enough space to embed + * the contents of orig. */ else { VALUE shared_root = ary_make_shared(orig); - if (ARY_OWNS_HEAP_P(copy)) { - ary_heap_free(copy); - } - else { - rb_ary_unshare_safe(copy); - } FL_UNSET_EMBED(copy); ARY_SET_PTR(copy, ARY_HEAP_PTR(orig)); ARY_SET_LEN(copy, ARY_HEAP_LEN(orig)); @@ -4457,8 +4710,10 @@ rb_ary_replace(VALUE copy, VALUE orig) * array.clear -> self * * Removes all elements from +self+: + * * a = [:foo, 'bar', 2] * a.clear # => [] + * */ VALUE @@ -4496,6 +4751,7 @@ rb_ary_clear(VALUE ary) * Replaces specified elements in +self+ with specified objects; returns +self+. * * With argument +obj+ and no block given, replaces all elements with that one object: + * * a = ['a', 'b', 'c', 'd'] * a # => ["a", "b", "c", "d"] * a.fill(:X) # => [:X, :X, :X, :X] @@ -4505,20 +4761,24 @@ rb_ary_clear(VALUE ary) * * If +start+ is in range (<tt>0 <= start < array.size</tt>), * replaces all elements from offset +start+ through the end: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, 2) # => ["a", "b", :X, :X] * * If +start+ is too large (<tt>start >= array.size</tt>), does nothing: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, 4) # => ["a", "b", "c", "d"] * a = ['a', 'b', 'c', 'd'] * a.fill(:X, 5) # => ["a", "b", "c", "d"] * * If +start+ is negative, counts from the end (starting index is <tt>start + array.size</tt>): + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, -2) # => ["a", "b", :X, :X] * * If +start+ is too small (less than and far from zero), replaces all elements: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, -6) # => [:X, :X, :X, :X] * a = ['a', 'b', 'c', 'd'] @@ -4528,20 +4788,24 @@ rb_ary_clear(VALUE ary) * replaces elements based on the given +start+ and +length+. * * If +start+ is in range, replaces +length+ elements beginning at offset +start+: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, 1, 1) # => ["a", :X, "c", "d"] * * If +start+ is negative, counts from the end: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, -2, 1) # => ["a", "b", :X, "d"] * * If +start+ is large (<tt>start >= array.size</tt>), extends +self+ with +nil+: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, 5, 0) # => ["a", "b", "c", "d", nil] * a = ['a', 'b', 'c', 'd'] * a.fill(:X, 5, 2) # => ["a", "b", "c", "d", nil, :X, :X] * * If +length+ is zero or negative, replaces no elements: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, 1, 0) # => ["a", "b", "c", "d"] * a.fill(:X, 1, -1) # => ["a", "b", "c", "d"] @@ -4551,14 +4815,17 @@ rb_ary_clear(VALUE ary) * * If the range is positive and ascending (<tt>0 < range.begin <= range.end</tt>), * replaces elements from <tt>range.begin</tt> to <tt>range.end</tt>: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, (1..1)) # => ["a", :X, "c", "d"] * * If <tt>range.first</tt> is negative, replaces no elements: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, (-1..1)) # => ["a", "b", "c", "d"] * * If <tt>range.last</tt> is negative, counts from the end: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, (0..-2)) # => [:X, :X, :X, "d"] * a = ['a', 'b', 'c', 'd'] @@ -4566,6 +4833,7 @@ rb_ary_clear(VALUE ary) * * If <tt>range.last</tt> and <tt>range.last</tt> are both negative, * both count from the end of the array: + * * a = ['a', 'b', 'c', 'd'] * a.fill(:X, (-1..-1)) # => ["a", "b", "c", :X] * a = ['a', 'b', 'c', 'd'] @@ -4573,29 +4841,34 @@ rb_ary_clear(VALUE ary) * * With no arguments and a block given, calls the block with each index; * replaces the corresponding element with the block's return value: + * * a = ['a', 'b', 'c', 'd'] * a.fill { |index| "new_#{index}" } # => ["new_0", "new_1", "new_2", "new_3"] * * With argument +start+ and a block given, calls the block with each index * from offset +start+ to the end; replaces the corresponding element - * with the block's return value: + * with the block's return value. * * If start is in range (<tt>0 <= start < array.size</tt>), * replaces from offset +start+ to the end: + * * a = ['a', 'b', 'c', 'd'] * a.fill(1) { |index| "new_#{index}" } # => ["a", "new_1", "new_2", "new_3"] * * If +start+ is too large(<tt>start >= array.size</tt>), does nothing: + * * a = ['a', 'b', 'c', 'd'] * a.fill(4) { |index| fail 'Cannot happen' } # => ["a", "b", "c", "d"] * a = ['a', 'b', 'c', 'd'] * a.fill(4) { |index| fail 'Cannot happen' } # => ["a", "b", "c", "d"] * * If +start+ is negative, counts from the end: + * * a = ['a', 'b', 'c', 'd'] * a.fill(-2) { |index| "new_#{index}" } # => ["a", "b", "new_2", "new_3"] * * If start is too small (<tt>start <= -array.size</tt>, replaces all elements: + * * a = ['a', 'b', 'c', 'd'] * a.fill(-6) { |index| "new_#{index}" } # => ["new_0", "new_1", "new_2", "new_3"] * a = ['a', 'b', 'c', 'd'] @@ -4606,20 +4879,24 @@ rb_ary_clear(VALUE ary) * replaces the corresponding element with the block's return value. * * If +start+ is in range, replaces +length+ elements beginning at offset +start+: + * * a = ['a', 'b', 'c', 'd'] * a.fill(1, 1) { |index| "new_#{index}" } # => ["a", "new_1", "c", "d"] * * If start is negative, counts from the end: + * * a = ['a', 'b', 'c', 'd'] * a.fill(-2, 1) { |index| "new_#{index}" } # => ["a", "b", "new_2", "d"] * * If +start+ is large (<tt>start >= array.size</tt>), extends +self+ with +nil+: + * * a = ['a', 'b', 'c', 'd'] * a.fill(5, 0) { |index| "new_#{index}" } # => ["a", "b", "c", "d", nil] * a = ['a', 'b', 'c', 'd'] * a.fill(5, 2) { |index| "new_#{index}" } # => ["a", "b", "c", "d", nil, "new_5", "new_6"] * * If +length+ is zero or less, replaces no elements: + * * a = ['a', 'b', 'c', 'd'] * a.fill(1, 0) { |index| "new_#{index}" } # => ["a", "b", "c", "d"] * a.fill(1, -1) { |index| "new_#{index}" } # => ["a", "b", "c", "d"] @@ -4630,14 +4907,17 @@ rb_ary_clear(VALUE ary) * * If the range is positive and ascending (<tt>range 0 < range.begin <= range.end</tt>, * replaces elements from <tt>range.begin</tt> to <tt>range.end</tt>: + * * a = ['a', 'b', 'c', 'd'] * a.fill(1..1) { |index| "new_#{index}" } # => ["a", "new_1", "c", "d"] * * If +range.first+ is negative, does nothing: + * * a = ['a', 'b', 'c', 'd'] * a.fill(-1..1) { |index| fail 'Cannot happen' } # => ["a", "b", "c", "d"] * * If <tt>range.last</tt> is negative, counts from the end: + * * a = ['a', 'b', 'c', 'd'] * a.fill(0..-2) { |index| "new_#{index}" } # => ["new_0", "new_1", "new_2", "d"] * a = ['a', 'b', 'c', 'd'] @@ -4645,10 +4925,12 @@ rb_ary_clear(VALUE ary) * * If <tt>range.first</tt> and <tt>range.last</tt> are both negative, * both count from the end: + * * a = ['a', 'b', 'c', 'd'] * a.fill(-1..-1) { |index| "new_#{index}" } # => ["a", "b", "c", "new_3"] * a = ['a', 'b', 'c', 'd'] * a.fill(-2..-2) { |index| "new_#{index}" } # => ["a", "b", "new_2", "d"] + * */ static VALUE @@ -4721,6 +5003,7 @@ rb_ary_fill(int argc, VALUE *argv, VALUE ary) * * Returns a new \Array containing all elements of +array+ * followed by all elements of +other_array+: + * * a = [0, 1] + [2, 3] * a # => [0, 1, 2, 3] * @@ -4761,6 +5044,7 @@ ary_append(VALUE x, VALUE y) * array.concat(*other_arrays) -> self * * Adds to +array+ all elements from each \Array in +other_arrays+; returns +self+: + * * a = [0, 1] * a.concat([2, 3], [4, 5]) # => [0, 1, 2, 3, 4, 5] */ @@ -4799,12 +5083,15 @@ rb_ary_concat(VALUE x, VALUE y) * * When non-negative argument \Integer +n+ is given, * returns a new \Array built by concatenating the +n+ copies of +self+: + * * a = ['x', 'y'] * a * 3 # => ["x", "y", "x", "y", "x", "y"] * * When \String argument +string_separator+ is given, * equivalent to <tt>array.join(string_separator)</tt>: + * * [0, [0, 1], {foo: 0}] * ', ' # => "0, 0, 1, {:foo=>0}" + * */ static VALUE @@ -4857,6 +5144,7 @@ rb_ary_times(VALUE ary, VALUE times) * * Returns the first element in +self+ that is an \Array * whose first element <tt>==</tt> +obj+: + * * a = [{foo: 0}, [2, 4], [4, 5, 6], [4, 5]] * a.assoc(4) # => [4, 5, 6] * @@ -4886,6 +5174,7 @@ rb_ary_assoc(VALUE ary, VALUE key) * * Returns the first element in +self+ that is an \Array * whose second element <tt>==</tt> +obj+: + * * a = [{foo: 0}, [2, 4], [4, 5, 6], [4, 5]] * a.rassoc(4) # => [2, 4] * @@ -4950,6 +5239,7 @@ recursive_equal(VALUE ary1, VALUE ary2, int recur) * * Returns +true+ if both <tt>array.size == other_array.size</tt> * and for each index +i+ in +array+, <tt>array[i] == other_array[i]</tt>: + * * a0 = [:foo, 'bar', 2] * a1 = [:foo, 'bar', 2.0] * a1 == a0 # => true @@ -4995,13 +5285,14 @@ recursive_eql(VALUE ary1, VALUE ary2, int recur) * * Returns +true+ if +self+ and +other_array+ are the same size, * and if, for each index +i+ in +self+, <tt>self[i].eql? other_array[i]</tt>: + * * a0 = [:foo, 'bar', 2] * a1 = [:foo, 'bar', 2] * a1.eql?(a0) # => true * * Otherwise, returns +false+. * - * This method is different from method {Array#==}[#method-i-3D-3D], + * This method is different from method Array#==, * which compares using method <tt>Object#==</tt>. */ @@ -5022,8 +5313,10 @@ rb_ary_eql(VALUE ary1, VALUE ary2) * Returns the integer hash value for +self+. * * Two arrays with the same content will have the same hash code (and will compare using eql?): + * * [0, 1, 2].hash == [0, 1, 2].hash # => true * [0, 1, 2].hash == [0, 1, 3].hash # => false + * */ static VALUE @@ -5049,6 +5342,7 @@ rb_ary_hash(VALUE ary) * * Returns +true+ if for some index +i+ in +self+, <tt>obj == self[i]</tt>; * otherwise +false+: + * * [0, 1, 2].include?(2) # => true * [0, 1, 2].include?(3) # => false */ @@ -5111,18 +5405,27 @@ recursive_cmp(VALUE ary1, VALUE ary2, int recur) * For each index +i+ in +self+, evaluates <tt>result = self[i] <=> other_array[i]</tt>. * * Returns -1 if any result is -1: + * * [0, 1, 2] <=> [0, 1, 3] # => -1 * * Returns 1 if any result is 1: + * * [0, 1, 2] <=> [0, 1, 1] # => 1 * * When all results are zero: + * * - Returns -1 if +array+ is smaller than +other_array+: + * * [0, 1, 2] <=> [0, 1, 2, 3] # => -1 + * * - Returns 1 if +array+ is larger than +other_array+: + * * [0, 1, 2] <=> [0, 1] # => 1 + * * - Returns 0 if +array+ and +other_array+ are the same size: + * * [0, 1, 2] <=> [0, 1, 2] # => 0 + * */ VALUE @@ -5209,6 +5512,7 @@ ary_recycle_hash(VALUE hash) * that are not found in \Array +other_array+; * items are compared using <tt>eql?</tt>; * the order from +array+ is preserved: + * * [0, 1, 1, 2, 1, 1, 3, 1, 1] - [1] # => [0, 2, 3] * [0, 1, 2, 3] - [3, 0] # => [1, 2] * [0, 1, 2] - [4] # => [0, 1, 2] @@ -5252,6 +5556,7 @@ rb_ary_diff(VALUE ary1, VALUE ary2) * Returns a new \Array containing only those elements from +self+ * that are not found in any of the Arrays +other_arrays+; * items are compared using <tt>eql?</tt>; order from +self+ is preserved: + * * [0, 1, 1, 2, 1, 1, 3, 1, 1].difference([1]) # => [0, 2, 3] * [0, 1, 2, 3].difference([3, 0], [1, 3]) # => [2] * [0, 1, 2].difference([4]) # => [0, 1, 2] @@ -5304,10 +5609,12 @@ rb_ary_difference_multi(int argc, VALUE *argv, VALUE ary) * * Returns a new \Array containing each element found in both +array+ and \Array +other_array+; * duplicates are omitted; items are compared using <tt>eql?</tt>: + * * [0, 1, 2, 3] & [1, 2] # => [1, 2] * [0, 1, 0, 1] & [0, 1] # => [0, 1] * * Preserves order from +array+: + * * [0, 1, 2] & [3, 2, 1, 0] # => [0, 1, 2] * * Related: Array#intersection. @@ -5356,10 +5663,12 @@ rb_ary_and(VALUE ary1, VALUE ary2) * Returns a new \Array containing each element found both in +self+ * and in all of the given Arrays +other_arrays+; * duplicates are omitted; items are compared using <tt>eql?</tt>: + * * [0, 1, 2, 3].intersection([0, 1, 2], [0, 1, 3]) # => [0, 1] * [0, 0, 1, 1, 2, 3].intersection([0, 1, 2], [0, 1, 3]) # => [0, 1] * * Preserves order from +self+: + * * [0, 1, 2].intersection([2, 1, 0]) # => [0, 1, 2] * * Returns a copy of +self+ if no arguments given. @@ -5418,6 +5727,7 @@ rb_ary_union_hash(VALUE hash, VALUE ary2) * Returns the union of +array+ and \Array +other_array+; * duplicates are removed; order is preserved; * items are compared using <tt>eql?</tt>: + * * [0, 1] | [2, 3] # => [0, 1, 2, 3] * [0, 1, 1] | [2, 2, 3] # => [0, 1, 2, 3] * [0, 1, 2] | [3, 2, 1, 0] # => [0, 1, 2, 3] @@ -5452,6 +5762,7 @@ rb_ary_or(VALUE ary1, VALUE ary2) * * Returns a new \Array that is the union of +self+ and all given Arrays +other_arrays+; * duplicates are removed; order is preserved; items are compared using <tt>eql?</tt>: + * * [0, 1, 2, 3].union([4, 5], [6, 7]) # => [0, 1, 2, 3, 4, 5, 6, 7] * [0, 1, 1].union([2, 1], [3, 1]) # => [0, 1, 2, 3] * [0, 1, 2, 3].union([3, 2], [1, 0]) # => [0, 1, 2, 3] @@ -5496,13 +5807,14 @@ rb_ary_union_multi(int argc, VALUE *argv, VALUE ary) * ary.intersect?(other_ary) -> true or false * * Returns +true+ if the array and +other_ary+ have at least one element in - * common, otherwise returns +false+. + * common, otherwise returns +false+: * * a = [ 1, 2, 3 ] * b = [ 3, 4, 5 ] * c = [ 5, 6, 7 ] * a.intersect?(b) #=> true * a.intersect?(c) #=> false + * */ static VALUE @@ -5643,6 +5955,7 @@ ary_max_opt_string(VALUE ary, long i, VALUE vmax) * array.max(n) {|a, b| ... } -> new_array * * Returns one of the following: + * * - The maximum-valued element from +self+. * - A new \Array of maximum-valued elements selected from +self+. * @@ -5651,10 +5964,12 @@ ary_max_opt_string(VALUE ary, long i, VALUE vmax) * * With no argument and no block, returns the element in +self+ * having the maximum value per method <tt><=></tt>: + * * [0, 1, 2].max # => 2 * * With an argument \Integer +n+ and no block, returns a new \Array with at most +n+ elements, * in descending order per method <tt><=></tt>: + * * [0, 1, 2, 3].max(3) # => [3, 2, 1] * [0, 1, 2, 3].max(6) # => [3, 2, 1, 0] * @@ -5662,11 +5977,14 @@ ary_max_opt_string(VALUE ary, long i, VALUE vmax) * * With a block and no argument, calls the block <tt>self.size-1</tt> times to compare elements; * returns the element having the maximum value per the block: + * * ['0', '00', '000'].max {|a, b| a.size <=> b.size } # => "000" * * With an argument +n+ and a block, returns a new \Array with at most +n+ elements, * in descending order per the block: + * * ['0', '00', '000'].max(2) {|a, b| a.size <=> b.size } # => ["000", "00"] + * */ static VALUE rb_ary_max(int argc, VALUE *argv, VALUE ary) @@ -5806,6 +6124,7 @@ ary_min_opt_string(VALUE ary, long i, VALUE vmin) * array.min(n) { |a, b| ... } -> new_array * * Returns one of the following: + * * - The minimum-valued element from +self+. * - A new \Array of minimum-valued elements selected from +self+. * @@ -5814,10 +6133,12 @@ ary_min_opt_string(VALUE ary, long i, VALUE vmin) * * With no argument and no block, returns the element in +self+ * having the minimum value per method <tt><=></tt>: + * * [0, 1, 2].min # => 0 * * With \Integer argument +n+ and no block, returns a new \Array with at most +n+ elements, * in ascending order per method <tt><=></tt>: + * * [0, 1, 2, 3].min(3) # => [0, 1, 2] * [0, 1, 2, 3].min(6) # => [0, 1, 2, 3] * @@ -5825,11 +6146,14 @@ ary_min_opt_string(VALUE ary, long i, VALUE vmin) * * With a block and no argument, calls the block <tt>self.size-1</tt> times to compare elements; * returns the element having the minimum value per the block: + * * ['0', '00', '000'].min { |a, b| a.size <=> b.size } # => "0" * * With an argument +n+ and a block, returns a new \Array with at most +n+ elements, * in ascending order per the block: + * * ['0', '00', '000'].min(2) {|a, b| a.size <=> b.size } # => ["0", "00"] + * */ static VALUE rb_ary_min(int argc, VALUE *argv, VALUE ary) @@ -5884,13 +6208,16 @@ rb_ary_min(int argc, VALUE *argv, VALUE ary) * with an \Integer; * returns a new 2-element \Array containing the minimum and maximum values * from +self+, per method <tt><=></tt>: + * * [0, 1, 2].minmax # => [0, 2] * * When a block is given, the block must return an \Integer; * the block is called <tt>self.size-1</tt> times to compare elements; * returns a new 2-element \Array containing the minimum and maximum values * from +self+, per the block: + * * ['0', '00', '000'].minmax {|a, b| a.size <=> b.size } # => ["0", "000"] + * */ static VALUE rb_ary_minmax(VALUE ary) @@ -5920,6 +6247,7 @@ push_value(st_data_t key, st_data_t val, st_data_t ary) * to compare. * * Returns +self+ if any elements removed: + * * a = [0, 0, 1, 1, 2, 2] * a.uniq! # => [0, 1, 2] * @@ -5930,6 +6258,7 @@ push_value(st_data_t key, st_data_t val, st_data_t ary) * elements for which the block returns duplicate values. * * Returns +self+ if any elements removed: + * * a = ['a', 'aa', 'aaa', 'b', 'bb', 'bbb'] * a.uniq! {|element| element.size } # => ['a', 'aa', 'aaa'] * @@ -5975,15 +6304,18 @@ rb_ary_uniq_bang(VALUE ary) * the first occurrence always being retained. * * With no block given, identifies and omits duplicates using method <tt>eql?</tt> - * to compare. + * to compare: + * * a = [0, 0, 1, 1, 2, 2] * a.uniq # => [0, 1, 2] * * With a block given, calls the block for each element; * identifies (using method <tt>eql?</tt>) and omits duplicate values, * that is, those elements for which the block returns the same value: + * * a = ['a', 'aa', 'aaa', 'b', 'bb', 'bbb'] * a.uniq {|element| element.size } # => ["a", "aa", "aaa"] + * */ static VALUE @@ -6047,6 +6379,7 @@ rb_ary_compact_bang(VALUE ary) * array.compact -> new_array * * Returns a new \Array containing all non-+nil+ elements from +self+: + * * a = [nil, 0, nil, 1, nil, 2, nil] * a.compact # => [0, 1, 2] */ @@ -6068,19 +6401,22 @@ rb_ary_compact(VALUE ary) * Returns a count of specified elements. * * With no argument and no block, returns the count of all elements: + * * [0, 1, 2].count # => 3 * [].count # => 0 * * With argument +obj+, returns the count of elements <tt>==</tt> to +obj+: + * * [0, 1, 2, 0.0].count(0) # => 2 * [0, 1, 2].count(3) # => 0 * * With no argument and a block given, calls the block with each element; * returns the count of elements for which the block returns a truthy value: + * * [0, 1, 2, 3].count {|element| element > 1} # => 2 * * With argument +obj+ and a block given, issues a warning, ignores the block, - * and returns the count of elements <tt>==</tt> to +obj+: + * and returns the count of elements <tt>==</tt> to +obj+. */ static VALUE @@ -6214,6 +6550,7 @@ flatten(VALUE ary, int level) * returns +self+ if any changes, +nil+ otherwise. * * With non-negative \Integer argument +level+, flattens recursively through +level+ levels: + * * a = [ 0, [ 1, [2, 3], 4 ], 5 ] * a.flatten!(1) # => [0, 1, [2, 3], 4, 5] * a = [ 0, [ 1, [2, 3], 4 ], 5 ] @@ -6223,6 +6560,7 @@ flatten(VALUE ary, int level) * [0, 1, 2].flatten!(1) # => nil * * With no argument, a +nil+ argument, or with negative argument +level+, flattens all levels: + * * a = [ 0, [ 1, [2, 3], 4 ], 5 ] * a.flatten! # => [0, 1, 2, 3, 4, 5] * [0, 1, 2].flatten! # => nil @@ -6231,6 +6569,7 @@ flatten(VALUE ary, int level) * a = [ 0, [ 1, [2, 3], 4 ], 5 ] * a.flatten!(-2) # => [0, 1, 2, 3, 4, 5] * [0, 1, 2].flatten!(-1) # => nil + * */ static VALUE @@ -6265,6 +6604,7 @@ rb_ary_flatten_bang(int argc, VALUE *argv, VALUE ary) * - Each \Array is replaced by its individual elements. * * With non-negative \Integer argument +level+, flattens recursively through +level+ levels: + * * a = [ 0, [ 1, [2, 3], 4 ], 5 ] * a.flatten(0) # => [0, [1, [2, 3], 4], 5] * a = [ 0, [ 1, [2, 3], 4 ], 5 ] @@ -6275,6 +6615,7 @@ rb_ary_flatten_bang(int argc, VALUE *argv, VALUE ary) * a.flatten(3) # => [0, 1, 2, 3, 4, 5] * * With no argument, a +nil+ argument, or with negative argument +level+, flattens all levels: + * * a = [ 0, [ 1, [2, 3], 4 ], 5 ] * a.flatten # => [0, 1, 2, 3, 4, 5] * [0, 1, 2].flatten # => [0, 1, 2] @@ -6283,6 +6624,7 @@ rb_ary_flatten_bang(int argc, VALUE *argv, VALUE ary) * a = [ 0, [ 1, [2, 3], 4 ], 5 ] * a.flatten(-2) # => [0, 1, 2, 3, 4, 5] * [0, 1, 2].flatten(-1) # => [0, 1, 2] + * */ static VALUE @@ -6498,15 +6840,18 @@ rb_ary_cycle_size(VALUE self, VALUE args, VALUE eobj) * When called with positive \Integer argument +count+ and a block, * calls the block with each element, then does so again, * until it has done so +count+ times; returns +nil+: + * * output = [] * [0, 1].cycle(2) {|element| output.push(element) } # => nil * output # => [0, 1, 0, 1] * * If +count+ is zero or negative, does not call the block: + * * [0, 1].cycle(0) {|element| fail 'Cannot happen' } # => nil * [0, 1].cycle(-1) {|element| fail 'Cannot happen' } # => nil * * When a block is given, and argument is omitted or +nil+, cycles forever: + * * # Prints 0 and 1 forever. * [0, 1].cycle {|element| puts element } * [0, 1].cycle(nil) {|element| puts element } @@ -6516,6 +6861,7 @@ rb_ary_cycle_size(VALUE self, VALUE args, VALUE eobj) * [0, 1].cycle(2) # => #<Enumerator: [0, 1]:cycle(2)> * [0, 1].cycle # => # => #<Enumerator: [0, 1]:cycle> * [0, 1].cycle.first(5) # => [0, 1, 0, 1, 0] + * */ static VALUE rb_ary_cycle(int argc, VALUE *argv, VALUE ary) @@ -6674,19 +7020,26 @@ rb_ary_permutation_size(VALUE ary, VALUE args, VALUE eobj) * are given, calls the block with all +n+-tuple permutations of +self+. * * Example: + * * a = [0, 1, 2] * a.permutation(2) {|permutation| p permutation } + * * Output: + * * [0, 1] * [0, 2] * [1, 0] * [1, 2] * [2, 0] * [2, 1] + * * Another example: + * * a = [0, 1, 2] * a.permutation(3) {|permutation| p permutation } + * * Output: + * * [0, 1, 2] * [0, 2, 1] * [1, 0, 2] @@ -6695,22 +7048,29 @@ rb_ary_permutation_size(VALUE ary, VALUE args, VALUE eobj) * [2, 1, 0] * * When +n+ is zero, calls the block once with a new empty \Array: + * * a = [0, 1, 2] * a.permutation(0) {|permutation| p permutation } + * * Output: + * * [] * * When +n+ is out of range (negative or larger than <tt>self.size</tt>), * does not call the block: + * * a = [0, 1, 2] * a.permutation(-1) {|permutation| fail 'Cannot happen' } * a.permutation(4) {|permutation| fail 'Cannot happen' } * * When a block given but no argument, * behaves the same as <tt>a.permutation(a.size)</tt>: + * * a = [0, 1, 2] * a.permutation {|permutation| p permutation } + * * Output: + * * [0, 1, 2] * [0, 2, 1] * [1, 0, 2] @@ -6719,9 +7079,11 @@ rb_ary_permutation_size(VALUE ary, VALUE args, VALUE eobj) * [2, 1, 0] * * Returns a new \Enumerator if no block given: + * * a = [0, 1, 2] * a.permutation # => #<Enumerator: [0, 1, 2]:permutation> * a.permutation(2) # => #<Enumerator: [0, 1, 2]:permutation(2)> + * */ static VALUE @@ -6804,34 +7166,46 @@ rb_ary_combination_size(VALUE ary, VALUE args, VALUE eobj) * are given, calls the block with all +n+-tuple combinations of +self+. * * Example: + * * a = [0, 1, 2] * a.combination(2) {|combination| p combination } + * * Output: + * * [0, 1] * [0, 2] * [1, 2] * * Another example: + * * a = [0, 1, 2] * a.combination(3) {|combination| p combination } + * * Output: + * * [0, 1, 2] * * When +n+ is zero, calls the block once with a new empty \Array: + * * a = [0, 1, 2] * a1 = a.combination(0) {|combination| p combination } + * * Output: + * * [] * * When +n+ is out of range (negative or larger than <tt>self.size</tt>), * does not call the block: + * * a = [0, 1, 2] * a.combination(-1) {|combination| fail 'Cannot happen' } * a.combination(4) {|combination| fail 'Cannot happen' } * * Returns a new \Enumerator if no block given: + * * a = [0, 1, 2] * a.combination(2) # => #<Enumerator: [0, 1, 2]:combination(2)> + * */ static VALUE @@ -6930,16 +7304,22 @@ rb_ary_repeated_permutation_size(VALUE ary, VALUE args, VALUE eobj) * The number of permutations is <tt>self.size**n</tt>. * * +n+ = 1: + * * a = [0, 1, 2] * a.repeated_permutation(1) {|permutation| p permutation } + * * Output: + * * [0] * [1] * [2] * * +n+ = 2: + * * a.repeated_permutation(2) {|permutation| p permutation } + * * Output: + * * [0, 0] * [0, 1] * [0, 2] @@ -6953,14 +7333,17 @@ rb_ary_repeated_permutation_size(VALUE ary, VALUE args, VALUE eobj) * If +n+ is zero, calls the block once with an empty \Array. * * If +n+ is negative, does not call the block: + * * a.repeated_permutation(-1) {|permutation| fail 'Cannot happen' } * * Returns a new \Enumerator if no block given: + * * a = [0, 1, 2] * a.repeated_permutation(2) # => #<Enumerator: [0, 1, 2]:permutation(2)> * * Using Enumerators, it's convenient to show the permutations and counts * for some values of +n+: + * * e = a.repeated_permutation(0) * e.size # => 1 * e.to_a # => [[]] @@ -6970,6 +7353,7 @@ rb_ary_repeated_permutation_size(VALUE ary, VALUE args, VALUE eobj) * e = a.repeated_permutation(2) * e.size # => 9 * e.to_a # => [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]] + * */ static VALUE rb_ary_repeated_permutation(VALUE ary, VALUE num) @@ -7052,16 +7436,22 @@ rb_ary_repeated_combination_size(VALUE ary, VALUE args, VALUE eobj) * The number of combinations is <tt>(n+1)(n+2)/2</tt>. * * +n+ = 1: + * * a = [0, 1, 2] * a.repeated_combination(1) {|combination| p combination } + * * Output: + * * [0] * [1] * [2] * * +n+ = 2: + * * a.repeated_combination(2) {|combination| p combination } + * * Output: + * * [0, 0] * [0, 1] * [0, 2] @@ -7072,14 +7462,17 @@ rb_ary_repeated_combination_size(VALUE ary, VALUE args, VALUE eobj) * If +n+ is zero, calls the block once with an empty \Array. * * If +n+ is negative, does not call the block: + * * a.repeated_combination(-1) {|combination| fail 'Cannot happen' } * * Returns a new \Enumerator if no block given: + * * a = [0, 1, 2] * a.repeated_combination(2) # => #<Enumerator: [0, 1, 2]:combination(2)> * * Using Enumerators, it's convenient to show the combinations and counts * for some values of +n+: + * * e = a.repeated_combination(0) * e.size # => 1 * e.to_a # => [[]] @@ -7089,6 +7482,7 @@ rb_ary_repeated_combination_size(VALUE ary, VALUE args, VALUE eobj) * e = a.repeated_combination(2) * e.size # => 6 * e.to_a # => [[0, 0], [0, 1], [0, 2], [1, 1], [1, 2], [2, 2]] + * */ static VALUE @@ -7132,12 +7526,14 @@ rb_ary_repeated_combination(VALUE ary, VALUE num) * array.product(*other_arrays) {|combination| ... } -> self * * Computes and returns or yields all combinations of elements from all the Arrays, - * including both +self+ and +other_arrays+. + * including both +self+ and +other_arrays+: + * * - The number of combinations is the product of the sizes of all the arrays, * including both +self+ and +other_arrays+. * - The order of the returned combinations is indeterminate. * * When no block is given, returns the combinations as an \Array of Arrays: + * * a = [0, 1, 2] * a1 = [3, 4] * a2 = [5, 6] @@ -7152,11 +7548,15 @@ rb_ary_repeated_combination(VALUE ary, VALUE num) * * If no argument is given, returns an \Array of 1-element Arrays, * each containing an element of +self+: + * * a.product # => [[0], [1], [2]] * * When a block is given, yields each combination as an \Array; returns +self+: + * * a.product(a1) {|combination| p combination } + * * Output: + * * [0, 3] * [0, 4] * [1, 3] @@ -7165,14 +7565,19 @@ rb_ary_repeated_combination(VALUE ary, VALUE num) * [2, 4] * * If any argument is an empty \Array, does not call the block: + * * a.product(a1, a2, []) {|combination| fail 'Cannot happen' } * * If no argument is given, yields each element of +self+ as a 1-element \Array: + * * a.product {|combination| p combination } + * * Output: + * * [0] * [1] * [2] + * */ static VALUE @@ -7230,13 +7635,13 @@ rb_ary_product(int argc, VALUE *argv, VALUE ary) /* put it on the result array */ if (NIL_P(result)) { - FL_SET(t0, FL_USER5); + FL_SET(t0, RARRAY_SHARED_ROOT_FLAG); rb_yield(subarray); - if (! FL_TEST(t0, FL_USER5)) { + if (!FL_TEST(t0, RARRAY_SHARED_ROOT_FLAG)) { rb_raise(rb_eRuntimeError, "product reentered"); } else { - FL_UNSET(t0, FL_USER5); + FL_UNSET(t0, RARRAY_SHARED_ROOT_FLAG); } } else { @@ -7272,11 +7677,13 @@ done: * does not modify +self+. * * Examples: + * * a = [0, 1, 2, 3, 4, 5] * a.take(1) # => [0] * a.take(2) # => [0, 1] * a.take(50) # => [0, 1, 2, 3, 4, 5] * a # => [0, 1, 2, 3, 4, 5] + * */ static VALUE @@ -7299,14 +7706,17 @@ rb_ary_take(VALUE obj, VALUE n) * * With a block given, calls the block with each successive element of +self+; * stops if the block returns +false+ or +nil+; - * returns a new Array containing those elements for which the block returned a truthy value: + * returns a new \Array containing those elements for which the block returned a truthy value: + * * a = [0, 1, 2, 3, 4, 5] * a.take_while {|element| element < 3 } # => [0, 1, 2] * a.take_while {|element| true } # => [0, 1, 2, 3, 4, 5] * a # => [0, 1, 2, 3, 4, 5] * * With no block given, returns a new \Enumerator: + * * [0, 1].take_while # => #<Enumerator: [0, 1]:take_while> + * */ static VALUE @@ -7330,10 +7740,12 @@ rb_ary_take_while(VALUE ary) * does not modify +self+. * * Examples: + * * a = [0, 1, 2, 3, 4, 5] * a.drop(0) # => [0, 1, 2, 3, 4, 5] * a.drop(1) # => [1, 2, 3, 4, 5] * a.drop(2) # => [2, 3, 4, 5] + * */ static VALUE @@ -7360,12 +7772,15 @@ rb_ary_drop(VALUE ary, VALUE n) * * With a block given, calls the block with each successive element of +self+; * stops if the block returns +false+ or +nil+; - * returns a new Array _omitting_ those elements for which the block returned a truthy value: + * returns a new \Array _omitting_ those elements for which the block returned a truthy value: + * * a = [0, 1, 2, 3, 4, 5] * a.drop_while {|element| element < 3 } # => [3, 4, 5] * * With no block given, returns a new \Enumerator: + * * [0, 1].drop_while # => # => #<Enumerator: [0, 1]:drop_while> + * */ static VALUE @@ -7390,17 +7805,20 @@ rb_ary_drop_while(VALUE ary) * * With no block given and no argument, returns +true+ if +self+ has any truthy element, * +false+ otherwise: + * * [nil, 0, false].any? # => true * [nil, false].any? # => false * [].any? # => false * * With a block given and no argument, calls the block with each element in +self+; * returns +true+ if the block returns any truthy value, +false+ otherwise: + * * [0, 1, 2].any? {|element| element > 1 } # => true * [0, 1, 2].any? {|element| element > 2 } # => false * * If argument +obj+ is given, returns +true+ if +obj+.<tt>===</tt> any element, * +false+ otherwise: + * * ['food', 'drink'].any?(/foo/) # => true * ['food', 'drink'].any?(/bar/) # => false * [].any?(/foo/) # => false @@ -7448,16 +7866,19 @@ rb_ary_any_p(int argc, VALUE *argv, VALUE ary) * * With no block given and no argument, returns +true+ if +self+ contains only truthy elements, * +false+ otherwise: + * * [0, 1, :foo].all? # => true * [0, nil, 2].all? # => false * [].all? # => true * * With a block given and no argument, calls the block with each element in +self+; * returns +true+ if the block returns only truthy values, +false+ otherwise: + * * [0, 1, 2].all? { |element| element < 3 } # => true * [0, 1, 2].all? { |element| element < 2 } # => false * * If argument +obj+ is given, returns +true+ if <tt>obj.===</tt> every element, +false+ otherwise: + * * ['food', 'fool', 'foot'].all?(/foo/) # => true * ['food', 'drink'].all?(/bar/) # => false * [].all?(/foo/) # => true @@ -7505,16 +7926,19 @@ rb_ary_all_p(int argc, VALUE *argv, VALUE ary) * * With no block given and no argument, returns +true+ if +self+ has no truthy elements, * +false+ otherwise: + * * [nil, false].none? # => true * [nil, 0, false].none? # => false * [].none? # => true * * With a block given and no argument, calls the block with each element in +self+; * returns +true+ if the block returns no truthy value, +false+ otherwise: + * * [0, 1, 2].none? {|element| element > 3 } # => true * [0, 1, 2].none? {|element| element > 1 } # => false * * If argument +obj+ is given, returns +true+ if <tt>obj.===</tt> no element, +false+ otherwise: + * * ['food', 'drink'].none?(/bar/) # => true * ['food', 'drink'].none?(/foo/) # => false * [].none?(/foo/) # => true @@ -7562,6 +7986,7 @@ rb_ary_none_p(int argc, VALUE *argv, VALUE ary) * * With no block given and no argument, returns +true+ if +self+ has exactly one truthy element, * +false+ otherwise: + * * [nil, 0].one? # => true * [0, 0].one? # => false * [nil, nil].one? # => false @@ -7569,12 +7994,14 @@ rb_ary_none_p(int argc, VALUE *argv, VALUE ary) * * With a block given and no argument, calls the block with each element in +self+; * returns +true+ if the block a truthy value for exactly one element, +false+ otherwise: + * * [0, 1, 2].one? {|element| element > 0 } # => false * [0, 1, 2].one? {|element| element > 1 } # => true * [0, 1, 2].one? {|element| element > 2 } # => false * * If argument +obj+ is given, returns +true+ if <tt>obj.===</tt> exactly one element, * +false+ otherwise: + * * [0, 1, 2].one?(0) # => true * [0, 0, 1].one?(0) # => false * [1, 1, 2].one?(0) # => false @@ -7633,11 +8060,13 @@ rb_ary_one_p(int argc, VALUE *argv, VALUE ary) * See {Dig Methods}[rdoc-ref:dig_methods.rdoc]. * * Examples: + * * a = [:foo, [:bar, :baz, [:bat, :bam]]] * a.dig(1) # => [:bar, :baz, [:bat, :bam]] * a.dig(1, 2) # => [:bat, :bam] * a.dig(1, 2, 0) # => :bat * a.dig(1, 2, 3) # => nil + * */ static VALUE @@ -7670,31 +8099,38 @@ finish_exact_sum(long n, VALUE r, VALUE v, int z) * array.sum(init = 0) {|element| ... } -> object * * When no block is given, returns the object equivalent to: + * * sum = init * array.each {|element| sum += element } * sum + * * For example, <tt>[e1, e2, e3].sum</tt> returns <tt>init + e1 + e2 + e3</tt>. * * Examples: + * * a = [0, 1, 2, 3] * a.sum # => 6 * a.sum(100) # => 106 * * The elements need not be numeric, but must be <tt>+</tt>-compatible * with each other and with +init+: + * * a = ['abc', 'def', 'ghi'] * a.sum('jkl') # => "jklabcdefghi" * * When a block is given, it is called with each element * and the block's return value (instead of the element itself) is used as the addend: + * * a = ['zero', 1, :two] * s = a.sum('Coerced and concatenated: ') {|element| element.to_s } * s # => "Coerced and concatenated: zero1two" * * Notes: + * * - Array#join and Array#flatten may be faster than Array#sum * for an \Array of Strings or an \Array of Arrays. * - Array#sum method may not respect method redefinition of "+" methods such as Integer#+. + * */ static VALUE @@ -7814,82 +8250,128 @@ rb_ary_deconstruct(VALUE ary) } /* - * An \Array is an ordered, integer-indexed collection of objects, - * called _elements_. Any object may be an \Array element. + * An \Array is an ordered, integer-indexed collection of objects, called _elements_. + * Any object (even another array) may be an array element, + * and an array can contain objects of different types. * * == \Array Indexes * * \Array indexing starts at 0, as in C or Java. * * A positive index is an offset from the first element: + * * - Index 0 indicates the first element. * - Index 1 indicates the second element. * - ... * * A negative index is an offset, backwards, from the end of the array: + * * - Index -1 indicates the last element. * - Index -2 indicates the next-to-last element. * - ... * - * A non-negative index is <i>in range</i> if it is smaller than + * A non-negative index is <i>in range</i> if and only if it is smaller than * the size of the array. For a 3-element array: + * * - Indexes 0 through 2 are in range. * - Index 3 is out of range. * - * A negative index is <i>in range</i> if its absolute value is + * A negative index is <i>in range</i> if and only if its absolute value is * not larger than the size of the array. For a 3-element array: + * * - Indexes -1 through -3 are in range. * - Index -4 is out of range. * - * == Creating Arrays - * - * You can create an \Array object explicitly with: - * - * - An {array literal}[doc/syntax/literals_rdoc.html#label-Array+Literals]. - * - * You can convert certain objects to Arrays with: - * - * - \Method {Array}[Kernel.html#method-i-Array]. - * - * An \Array can contain different types of objects. For - * example, the array below contains an Integer, a String and a Float: - * - * ary = [1, "two", 3.0] #=> [1, "two", 3.0] - * - * An array can also be created by calling Array.new with zero, one - * (the initial size of the Array) or two arguments (the initial size and a - * default object). - * - * ary = Array.new #=> [] - * Array.new(3) #=> [nil, nil, nil] - * Array.new(3, true) #=> [true, true, true] - * - * Note that the second argument populates the array with references to the - * same object. Therefore, it is only recommended in cases when you need to - * instantiate arrays with natively immutable objects such as Symbols, - * numbers, true or false. + * 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]. * - * To create an array with separate objects a block can be passed instead. - * This method is safe to use with mutable objects such as hashes, strings or - * other arrays: * - * Array.new(4) {Hash.new} #=> [{}, {}, {}, {}] - * Array.new(4) {|i| i.to_s } #=> ["0", "1", "2", "3"] - * - * This is also a quick way to build up multi-dimensional arrays: - * - * empty_table = Array.new(3) {Array.new(3)} - * #=> [[nil, nil, nil], [nil, nil, nil], [nil, nil, nil]] + * == Creating Arrays * - * An array can also be created by using the Array() method, provided by - * Kernel, which tries to call #to_ary, then #to_a on its argument. + * You can create an \Array object explicitly with: * - * Array({:a => "a", :b => "b"}) #=> [[:a, "a"], [:b, "b"]] + * - An {array literal}[rdoc-ref:literals.rdoc@Array+Literals]: + * + * [1, 'one', :one, [2, 'two', :two]] + * + * - A {%w or %W: string-array Literal}[rdoc-ref:literals.rdoc@25w+and+-25W-3A+String-Array+Literals]: + * + * %w[foo bar baz] # => ["foo", "bar", "baz"] + * %w[1 % *] # => ["1", "%", "*"] + * + * - A {%i pr %I: symbol-array Literal}[rdoc-ref:literals.rdoc@25i+and+-25I-3A+Symbol-Array+Literals]: + * + * %i[foo bar baz] # => [:foo, :bar, :baz] + * %i[1 % *] # => [:"1", :%, :*] + * + * - \Method Kernel#Array: + * + * Array(["a", "b"]) # => ["a", "b"] + * Array(1..5) # => [1, 2, 3, 4, 5] + * Array(key: :value) # => [[:key, :value]] + * Array(nil) # => [] + * Array(1) # => [1] + * Array({:a => "a", :b => "b"}) # => [[:a, "a"], [:b, "b"]] + * + * - \Method Array.new: + * + * Array.new # => [] + * Array.new(3) # => [nil, nil, nil] + * Array.new(4) {Hash.new} # => [{}, {}, {}, {}] + * Array.new(3, true) # => [true, true, true] + * + * Note that the last example above populates the array + * with references to the same object. + * This is recommended only in cases where that object is a natively immutable object + * such as a symbol, a numeric, +nil+, +true+, or +false+. + * + * Another way to create an array with various objects, using a block; + * this usage is safe for mutable objects such as hashes, strings or + * other arrays: + * + * Array.new(4) {|i| i.to_s } # => ["0", "1", "2", "3"] + * + * Here is a way to create a multi-dimensional array: + * + * Array.new(3) {Array.new(3)} + * # => [[nil, nil, nil], [nil, nil, nil], [nil, nil, nil]] + * + * A number of Ruby methods, both in the core and in the standard library, + * provide instance method +to_a+, which converts an object to an array. + * + * - ARGF#to_a + * - Array#to_a + * - Enumerable#to_a + * - Hash#to_a + * - MatchData#to_a + * - NilClass#to_a + * - OptionParser#to_a + * - Range#to_a + * - Set#to_a + * - Struct#to_a + * - Time#to_a + * - Benchmark::Tms#to_a + * - CSV::Table#to_a + * - Enumerator::Lazy#to_a + * - Gem::List#to_a + * - Gem::NameTuple#to_a + * - Gem::Platform#to_a + * - Gem::RequestSet::Lockfile::Tokenizer#to_a + * - Gem::SourceList#to_a + * - OpenSSL::X509::Extension#to_a + * - OpenSSL::X509::Name#to_a + * - Racc::ISet#to_a + * - Rinda::RingFinger#to_a + * - Ripper::Lexer::Elem#to_a + * - RubyVM::InstructionSequence#to_a + * - YAML::DBM#to_a * * == Example Usage * * In addition to the methods it mixes in through the Enumerable module, the - * Array class has proprietary methods for accessing, searching and otherwise + * \Array class has proprietary methods for accessing, searching and otherwise * manipulating arrays. * * Some of the more common ones are illustrated below. @@ -7937,7 +8419,7 @@ rb_ary_deconstruct(VALUE ary) * * arr.drop(3) #=> [4, 5, 6] * - * == Obtaining Information about an Array + * == Obtaining Information about an \Array * * Arrays keep track of their own length at all times. To query an array * about the number of elements it contains, use #length, #count or #size. @@ -7975,7 +8457,7 @@ rb_ary_deconstruct(VALUE ary) * arr.insert(3, 'orange', 'pear', 'grapefruit') * #=> [0, 1, 2, "orange", "pear", "grapefruit", "apple", 3, 4, 5, 6] * - * == Removing Items from an Array + * == Removing Items from an \Array * * The method #pop removes the last element in an array and returns it: * @@ -8017,9 +8499,9 @@ rb_ary_deconstruct(VALUE ary) * * == Iterating over Arrays * - * Like all classes that include the Enumerable module, Array has an each + * Like all classes that include the Enumerable module, \Array has an each * method, which defines what elements should be iterated over and how. In - * case of Array's #each, all elements in the Array instance are yielded to + * case of Array's #each, all elements in the \Array instance are yielded to * the supplied block in sequence. * * Note that this operation leaves the array unchanged. @@ -8045,7 +8527,8 @@ rb_ary_deconstruct(VALUE ary) * arr.map! {|a| a**2} #=> [1, 4, 9, 16, 25] * arr #=> [1, 4, 9, 16, 25] * - * == Selecting Items from an Array + * + * == Selecting Items from an \Array * * Elements can be selected from an array according to criteria defined in a * block. The selection can happen in a destructive or a non-destructive @@ -8080,181 +8563,184 @@ rb_ary_deconstruct(VALUE ary) * * First, what's elsewhere. \Class \Array: * - * - Inherits from {class Object}[Object.html#class-Object-label-What-27s+Here]. - * - Includes {module Enumerable}[Enumerable.html#module-Enumerable-label-What-27s+Here], + * - Inherits from {class Object}[rdoc-ref:Object@What-27s+Here]. + * - Includes {module Enumerable}[rdoc-ref:Enumerable@What-27s+Here], * which provides dozens of additional methods. * * Here, class \Array provides methods that are useful for: * - * - {Creating an Array}[#class-Array-label-Methods+for+Creating+an+Array] - * - {Querying}[#class-Array-label-Methods+for+Querying] - * - {Comparing}[#class-Array-label-Methods+for+Comparing] - * - {Fetching}[#class-Array-label-Methods+for+Fetching] - * - {Assigning}[#class-Array-label-Methods+for+Assigning] - * - {Deleting}[#class-Array-label-Methods+for+Deleting] - * - {Combining}[#class-Array-label-Methods+for+Combining] - * - {Iterating}[#class-Array-label-Methods+for+Iterating] - * - {Converting}[#class-Array-label-Methods+for+Converting] - * - {And more....}[#class-Array-label-Other+Methods] + * - {Creating an Array}[rdoc-ref:Array@Methods+for+Creating+an+Array] + * - {Querying}[rdoc-ref:Array@Methods+for+Querying] + * - {Comparing}[rdoc-ref:Array@Methods+for+Comparing] + * - {Fetching}[rdoc-ref:Array@Methods+for+Fetching] + * - {Assigning}[rdoc-ref:Array@Methods+for+Assigning] + * - {Deleting}[rdoc-ref:Array@Methods+for+Deleting] + * - {Combining}[rdoc-ref:Array@Methods+for+Combining] + * - {Iterating}[rdoc-ref:Array@Methods+for+Iterating] + * - {Converting}[rdoc-ref:Array@Methods+for+Converting] + * - {And more....}[rdoc-ref:Array@Other+Methods] * - * === Methods for Creating an Array + * === Methods for Creating an \Array * - * ::[]:: Returns a new array populated with given objects. - * ::new:: Returns a new array. - * ::try_convert:: Returns a new array created from a given object. + * - ::[]: Returns a new array populated with given objects. + * - ::new: Returns a new array. + * - ::try_convert: Returns a new array created from a given object. * * === Methods for Querying * - * #length, #size:: Returns the count of elements. - * #include?:: Returns whether any element <tt>==</tt> a given object. - * #empty?:: Returns whether there are no elements. - * #all?:: Returns whether all elements meet a given criterion. - * #any?:: Returns whether any element meets a given criterion. - * #none?:: Returns whether no element <tt>==</tt> a given object. - * #one?:: Returns whether exactly one element <tt>==</tt> a given object. - * #count:: Returns the count of elements that meet a given criterion. - * #find_index, #index:: Returns the index of the first element that meets a given criterion. - * #rindex:: Returns the index of the last element that meets a given criterion. - * #hash:: Returns the integer hash code. + * - #length, #size: Returns the count of elements. + * - #include?: Returns whether any element <tt>==</tt> a given object. + * - #empty?: Returns whether there are no elements. + * - #all?: Returns whether all elements meet a given criterion. + * - #any?: Returns whether any element meets a given criterion. + * - #none?: Returns whether no element <tt>==</tt> a given object. + * - #one?: Returns whether exactly one element <tt>==</tt> a given object. + * - #count: Returns the count of elements that meet a given criterion. + * - #find_index, #index: Returns the index of the first element that meets a given criterion. + * - #rindex: Returns the index of the last element that meets a given criterion. + * - #hash: Returns the integer hash code. * * === Methods for Comparing - * {#<=>}[#method-i-3C-3D-3E]:: Returns -1, 0, or 1 - * as +self+ is less than, equal to, or greater than a given object. - * {#==}[#method-i-3D-3D]:: Returns whether each element in +self+ is <tt>==</tt> to the - * corresponding element in a given object. - * #eql?:: Returns whether each element in +self+ is <tt>eql?</tt> to the corresponding - * element in a given object. + * + * - #<=>: Returns -1, 0, or 1 * as +self+ is less than, equal to, or + * greater than a given object. + * - #==: Returns whether each element in +self+ is <tt>==</tt> to the corresponding element + * in a given object. + * - #eql?: Returns whether each element in +self+ is <tt>eql?</tt> to the corresponding + * element in a given object. * === Methods for Fetching * * These methods do not modify +self+. * - * #[]:: Returns one or more elements. - * #fetch:: Returns the element at a given offset. - * #first:: Returns one or more leading elements. - * #last:: Returns one or more trailing elements. - * #max:: Returns one or more maximum-valued elements, - * as determined by <tt><=></tt> or a given block. - * #max:: Returns one or more minimum-valued elements, - * as determined by <tt><=></tt> or a given block. - * #minmax:: Returns the minimum-valued and maximum-valued elements, - * as determined by <tt><=></tt> or a given block. - * #assoc:: Returns the first element that is an array - * whose first element <tt>==</tt> a given object. - * #rassoc:: Returns the first element that is an array - * whose second element <tt>==</tt> a given object. - * #at:: Returns the element at a given offset. - * #values_at:: Returns the elements at given offsets. - * #dig:: Returns the object in nested objects - * that is specified by a given index and additional arguments. - * #drop:: Returns trailing elements as determined by a given index. - * #take:: Returns leading elements as determined by a given index. - * #drop_while:: Returns trailing elements as determined by a given block. - * #take_while:: Returns leading elements as determined by a given block. - * #slice:: Returns consecutive elements as determined by a given argument. - * #sort:: Returns all elements in an order determined by <tt><=></tt> or a given block. - * #reverse:: Returns all elements in reverse order. - * #compact:: Returns an array containing all non-+nil+ elements. - * #select, #filter:: Returns an array containing elements selected by a given block. - * #uniq:: Returns an array containing non-duplicate elements. - * #rotate:: Returns all elements with some rotated from one end to the other. - * #bsearch:: Returns an element selected via a binary search - * as determined by a given block. - * #bsearch_index:: Returns the index of an element selected via a binary search - * as determined by a given block. - * #sample:: Returns one or more random elements. - * #shuffle:: Returns elements in a random order. + * - #[]: Returns one or more elements. + * - #fetch: Returns the element at a given offset. + * - #first: Returns one or more leading elements. + * - #last: Returns one or more trailing elements. + * - #max: Returns one or more maximum-valued elements, + * as determined by <tt><=></tt> or a given block. + * - #min: Returns one or more minimum-valued elements, + * as determined by <tt><=></tt> or a given block. + * - #minmax: Returns the minimum-valued and maximum-valued elements, + * as determined by <tt><=></tt> or a given block. + * - #assoc: Returns the first element that is an array + * whose first element <tt>==</tt> a given object. + * - #rassoc: Returns the first element that is an array + * whose second element <tt>==</tt> a given object. + * - #at: Returns the element at a given offset. + * - #values_at: Returns the elements at given offsets. + * - #dig: Returns the object in nested objects + * that is specified by a given index and additional arguments. + * - #drop: Returns trailing elements as determined by a given index. + * - #take: Returns leading elements as determined by a given index. + * - #drop_while: Returns trailing elements as determined by a given block. + * - #take_while: Returns leading elements as determined by a given block. + * - #slice: Returns consecutive elements as determined by a given argument. + * - #sort: Returns all elements in an order determined by <tt><=></tt> or a given block. + * - #reverse: Returns all elements in reverse order. + * - #compact: Returns an array containing all non-+nil+ elements. + * - #select, #filter: Returns an array containing elements selected by a given block. + * - #uniq: Returns an array containing non-duplicate elements. + * - #rotate: Returns all elements with some rotated from one end to the other. + * - #bsearch: Returns an element selected via a binary search + * as determined by a given block. + * - #bsearch_index: Returns the index of an element selected via a binary search + * as determined by a given block. + * - #sample: Returns one or more random elements. + * - #shuffle: Returns elements in a random order. * * === Methods for Assigning * * These methods add, replace, or reorder elements in +self+. * - * #[]=:: Assigns specified elements with a given object. - * #push, #append, #<<:: Appends trailing elements. - * #unshift, #prepend:: Prepends leading elements. - * #insert:: Inserts given objects at a given offset; does not replace elements. - * #concat:: Appends all elements from given arrays. - * #fill:: Replaces specified elements with specified objects. - * #replace:: Replaces the content of +self+ with the content of a given array. - * #reverse!:: Replaces +self+ with its elements reversed. - * #rotate!:: Replaces +self+ with its elements rotated. - * #shuffle!:: Replaces +self+ with its elements in random order. - * #sort!:: Replaces +self+ with its elements sorted, - * as determined by <tt><=></tt> or a given block. - * #sort_by!:: Replaces +self+ with its elements sorted, as determined by a given block. + * - #[]=: Assigns specified elements with a given object. + * - #push, #append, #<<: Appends trailing elements. + * - #unshift, #prepend: Prepends leading elements. + * - #insert: Inserts given objects at a given offset; does not replace elements. + * - #concat: Appends all elements from given arrays. + * - #fill: Replaces specified elements with specified objects. + * - #replace: Replaces the content of +self+ with the content of a given array. + * - #reverse!: Replaces +self+ with its elements reversed. + * - #rotate!: Replaces +self+ with its elements rotated. + * - #shuffle!: Replaces +self+ with its elements in random order. + * - #sort!: Replaces +self+ with its elements sorted, + * as determined by <tt><=></tt> or a given block. + * - #sort_by!: Replaces +self+ with its elements sorted, as determined by a given block. * * === Methods for Deleting * * Each of these methods removes elements from +self+: * - * #pop:: Removes and returns the last element. - * #shift:: Removes and returns the first element. - * #compact!:: Removes all non-+nil+ elements. - * #delete:: Removes elements equal to a given object. - * #delete_at:: Removes the element at a given offset. - * #delete_if:: Removes elements specified by a given block. - * #keep_if:: Removes elements not specified by a given block. - * #reject!:: Removes elements specified by a given block. - * #select!, #filter!:: Removes elements not specified by a given block. - * #slice!:: Removes and returns a sequence of elements. - * #uniq!:: Removes duplicates. + * - #pop: Removes and returns the last element. + * - #shift: Removes and returns the first element. + * - #compact!: Removes all non-+nil+ elements. + * - #delete: Removes elements equal to a given object. + * - #delete_at: Removes the element at a given offset. + * - #delete_if: Removes elements specified by a given block. + * - #keep_if: Removes elements not specified by a given block. + * - #reject!: Removes elements specified by a given block. + * - #select!, #filter!: Removes elements not specified by a given block. + * - #slice!: Removes and returns a sequence of elements. + * - #uniq!: Removes duplicates. * * === Methods for Combining * - * {#&}[#method-i-26]:: Returns an array containing elements found both in +self+ and a given array. - * #intersection:: Returns an array containing elements found both in +self+ - * and in each given array. - * #+:: Returns an array containing all elements of +self+ followed by all elements of a given array. - * #-:: Returns an array containiing all elements of +self+ that are not found in a given array. - * {#|}[#method-i-7C]:: Returns an array containing all elements of +self+ and all elements of a given array, - * duplicates removed. - * #union:: Returns an array containing all elements of +self+ and all elements of given arrays, - * duplicates removed. - * #difference:: Returns an array containing all elements of +self+ that are not found - * in any of the given arrays.. - * #product:: Returns or yields all combinations of elements from +self+ and given arrays. + * - #&: Returns an array containing elements found both in +self+ and a given array. + * - #intersection: Returns an array containing elements found both in +self+ + * and in each given array. + * - #+: Returns an array containing all elements of +self+ followed by all elements of a given array. + * - #-: Returns an array containing all elements of +self+ that are not found in a given array. + * - #|: Returns an array containing all elements of +self+ and all elements of a given array, + * duplicates removed. + * - #union: Returns an array containing all elements of +self+ and all elements of given arrays, + * duplicates removed. + * - #difference: Returns an array containing all elements of +self+ that are not found + * in any of the given arrays.. + * - #product: Returns or yields all combinations of elements from +self+ and given arrays. * * === Methods for Iterating * - * #each:: Passes each element to a given block. - * #reverse_each:: Passes each element, in reverse order, to a given block. - * #each_index:: Passes each element index to a given block. - * #cycle:: Calls a given block with each element, then does so again, - * for a specified number of times, or forever. - * #combination:: Calls a given block with combinations of elements of +self+; - * a combination does not use the same element more than once. - * #permutation:: Calls a given block with permutations of elements of +self+; - * a permutation does not use the same element more than once. - * #repeated_combination:: Calls a given block with combinations of elements of +self+; - * a combination may use the same element more than once. - * #repeated_permutation:: Calls a given block with permutations of elements of +self+; - * a permutation may use the same element more than once. + * - #each: Passes each element to a given block. + * - #reverse_each: Passes each element, in reverse order, to a given block. + * - #each_index: Passes each element index to a given block. + * - #cycle: Calls a given block with each element, then does so again, + * for a specified number of times, or forever. + * - #combination: Calls a given block with combinations of elements of +self+; + * a combination does not use the same element more than once. + * - #permutation: Calls a given block with permutations of elements of +self+; + * a permutation does not use the same element more than once. + * - #repeated_combination: Calls a given block with combinations of elements of +self+; + * a combination may use the same element more than once. + * - #repeated_permutation: Calls a given block with permutations of elements of +self+; + * a permutation may use the same element more than once. * * === Methods for Converting * - * #map, #collect:: Returns an array containing the block return-value for each element. - * #map!, #collect!:: Replaces each element with a block return-value. - * #flatten:: Returns an array that is a recursive flattening of +self+. - * #flatten!:: Replaces each nested array in +self+ with the elements from that array. - * #inspect, #to_s:: Returns a new String containing the elements. - * #join:: Returns a newsString containing the elements joined by the field separator. - * #to_a:: Returns +self+ or a new array containing all elements. - * #to_ary:: Returns +self+. - * #to_h:: Returns a new hash formed from the elements. - * #transpose:: Transposes +self+, which must be an array of arrays. - * #zip:: Returns a new array of arrays containing +self+ and given arrays; - * follow the link for details. + * - #map, #collect: Returns an array containing the block return-value for each element. + * - #map!, #collect!: Replaces each element with a block return-value. + * - #flatten: Returns an array that is a recursive flattening of +self+. + * - #flatten!: Replaces each nested array in +self+ with the elements from that array. + * - #inspect, #to_s: Returns a new String containing the elements. + * - #join: Returns a newsString containing the elements joined by the field separator. + * - #to_a: Returns +self+ or a new array containing all elements. + * - #to_ary: Returns +self+. + * - #to_h: Returns a new hash formed from the elements. + * - #transpose: Transposes +self+, which must be an array of arrays. + * - #zip: Returns a new array of arrays containing +self+ and given arrays; + * follow the link for details. * * === Other Methods * - * #*:: Returns one of the following: - * - With integer argument +n+, a new array that is the concatenation - * of +n+ copies of +self+. - * - With string argument +field_separator+, a new string that is equivalent to - * <tt>join(field_separator)</tt>. - * #abbrev:: Returns a hash of unambiguous abbreviations for elements. - * #pack:: Packs the elements into a binary sequence. - * #sum:: Returns a sum of elements according to either <tt>+</tt> or a given block. + * - #*: Returns one of the following: + * + * - With integer argument +n+, a new array that is the concatenation + * of +n+ copies of +self+. + * - With string argument +field_separator+, a new string that is equivalent to + * <tt>join(field_separator)</tt>. + * + * - #abbrev: Returns a hash of unambiguous abbreviations for elements. + * - #pack: Packs the elements into a binary sequence. + * - #sum: Returns a sum of elements according to either <tt>+</tt> or a given block. */ void @@ -8264,6 +8750,7 @@ Init_Array(void) rb_include_module(rb_cArray, rb_mEnumerable); rb_define_alloc_func(rb_cArray, empty_ary_alloc); + rb_define_singleton_method(rb_cArray, "new", rb_ary_s_new, -1); rb_define_singleton_method(rb_cArray, "[]", rb_ary_s_create, -1); rb_define_singleton_method(rb_cArray, "try_convert", rb_ary_s_try_convert, 1); rb_define_method(rb_cArray, "initialize", rb_ary_initialize, -1); @@ -215,14 +215,14 @@ ast_s_of(rb_execution_context_t *ec, VALUE module, VALUE body, VALUE keep_script iseq = rb_method_iseq(body); } if (iseq) { - node_id = iseq->body->location.node_id; + node_id = ISEQ_BODY(iseq)->location.node_id; } } if (!iseq) { return Qnil; } - lines = iseq->body->variable.script_lines; + lines = ISEQ_BODY(iseq)->variable.script_lines; VALUE path = rb_iseq_path(iseq); int e_option = RSTRING_LEN(path) == 2 && memcmp(RSTRING_PTR(path), "-e", 2) == 0; diff --git a/benchmark/README.md b/benchmark/README.md index c222164be3..e11381cad9 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -28,16 +28,18 @@ See also: ```console Usage: benchmark-driver [options] RUBY|YAML... - -r, --runner TYPE Specify runner type: ips, time, memory, once (default: ips) - -o, --output TYPE Specify output type: compare, simple, markdown, record (default: compare) + -r, --runner TYPE Specify runner type: ips, time, memory, once, block (default: ips) + -o, --output TYPE Specify output type: compare, simple, markdown, record, all (default: compare) -e, --executables EXECS Ruby executables (e1::path1 arg1; e2::path2 arg2;...) --rbenv VERSIONS Ruby executables in rbenv (x.x.x arg1;y.y.y arg2;...) --repeat-count NUM Try benchmark NUM times and use the fastest result or the worst memory usage --repeat-result TYPE Yield "best", "average" or "worst" result with --repeat-count (default: best) + --alternate Alternate executables instead of running the same executable in a row with --repeat-count --bundler Install and use gems specified in Gemfile --filter REGEXP Filter out benchmarks with given regexp --run-duration SECONDS Warmup estimates loop_count to run for this duration (default: 3) - -v, --verbose Verbose mode. Multiple -v options increase visibility (max: 2) + --timeout SECONDS Timeout ruby command execution with timeout(1) + -v, --verbose Verbose mode. Multiple -v options increase visilibity (max: 2) ``` ## make benchmark diff --git a/benchmark/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/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/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/vm_ivar_embedded_obj_init.yml b/benchmark/vm_ivar_embedded_obj_init.yml new file mode 100644 index 0000000000..eed6d4c0b9 --- /dev/null +++ b/benchmark/vm_ivar_embedded_obj_init.yml @@ -0,0 +1,12 @@ +prelude: | + class C + def initialize + @a = nil + @b = nil + @c = nil + end + end +benchmark: + vm_ivar_embedded_obj_init: | + C.new +loop_count: 30000000 diff --git a/benchmark/vm_ivar_init.yml b/benchmark/vm_ivar_extended_obj_init.yml index c6f1633907..994e9e6c49 100644 --- a/benchmark/vm_ivar_init.yml +++ b/benchmark/vm_ivar_extended_obj_init.yml @@ -9,6 +9,6 @@ prelude: | end end benchmark: - vm_ivar_init: | + vm_ivar_extended_obj_init: | C.new loop_count: 30000000 @@ -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 @@ -145,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 @@ -1573,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) @@ -1639,6 +1645,12 @@ 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 *const zds, const size_t zn, @@ -1656,8 +1668,14 @@ bary_mul_balance_with_mulfunc(BDIGIT *const zds, const size_t zn, BDIGITS_ZERO(zds, xn); if (wn < xn) { - const size_t r = (yn % xn) ? (yn % xn) : xn; - if ((2 * xn + yn + r) > zn) { + /* 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); } @@ -2291,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) { @@ -2556,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); @@ -2776,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) { @@ -2860,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; @@ -3957,7 +3975,7 @@ str2big_karatsuba( return z; } -#ifdef USE_GMP +#if USE_GMP static VALUE str2big_gmp( int sign, @@ -4222,7 +4240,7 @@ rb_int_parse_cstr(const char *str, ssize_t len, char **endp, size_t *ndigits, 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); @@ -4402,7 +4420,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) { @@ -5012,7 +5030,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) { @@ -5083,7 +5101,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); } @@ -6940,7 +6958,7 @@ rb_big_isqrt(VALUE n) 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) { @@ -6966,7 +6984,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; @@ -7172,7 +7190,7 @@ Init_Bignum(void) { 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/bootstraptest/runner.rb b/bootstraptest/runner.rb index acc113b729..3d42390254 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,45 @@ if !Dir.respond_to?(:mktmpdir) end end +# Configuration +BT = Struct.new(:ruby, + :verbose, + :color, + :tty, + :quiet, + :wn, + :progress, + :progress_bs, + :passed, + :failed, + :reset, + :columns, + :width, + :platform, + ).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 +111,28 @@ 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+)?/ + wn = $1.to_i + if wn <= 0 + require 'etc' + wn = [Etc.nprocessors / 2, 1].max + end + BT.wn = wn 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,...] @@ -128,15 +161,16 @@ End 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? + + 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 +179,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,290 +196,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) - unless @quiet - $stderr.printf("%s%-*s ", erase(@quiet), @width, @basename) - $stderr.flush - end - @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}" unless @quiet - else - msg = "FAIL #{@error-error}/#{@count-count}" - $stderr.print "#{@progress_bs}#{@failed}#{msg}#{@reset}" - @columns = 0 + 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 - $stderr.puts if !@quiet and (@tty or @error == error) end - $stderr.print(erase) if @quiet - @errbuf.each do |msg| - $stderr.puts msg + + Assertion.all.to_a.shuffle.each do |path, assertions| + assertions.each do |as| + aq << as + end end - if @error == 0 - if @count == 0 - $stderr.puts "No tests, no problem" unless @quiet - else - if @quiet - $stdout.puts "#{@passed}PASS#{@reset} all #{@count} tests" + + $stderr.print ' ' unless BT.quiet + 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 + $stderr.print '.' + end else - $stderr.puts "#{@passed}PASS#{@reset} all #{@count} tests" + term_wn += 1 end end - exit true - else - $stderr.puts "#{@failed}FAIL#{@reset} #{@error}/#{@count} tests failed" - exit false + ensure + ts.each(&:kill) + ts.each(&:join) end end -def show_progress(message = '') - if @quiet - # do nothing - elsif @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 @quiet - # do nothing - elsif @tty - $stderr.print "#{@progress_bs}#{@progress[@count % @progress.size]}" - elsif @verbose - $stderr.printf(". %.3f\n", t) +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 if BT.wn > 1 + 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 + end + + # show results + unless BT.quiet + $stderr.puts(erase) + + sec = Time.now - $start_time + $stderr.puts "Finished in #{'%.2f' % sec} sec\n\n" if Assertion.count > 0 + end + + Assertion.errbuf.each do |msg| + $stderr.puts msg + end + + out = BT.quiet ? $stdout : $stderr + + if BT_STATE.error == 0 + if Assertion.count == 0 + out.puts "No tests, no problem" unless BT.quiet else - $stderr.print '.' + out.puts "#{BT.passed}PASS#{BT.reset} all #{Assertion.count} tests" end + true 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) + $stderr.puts "#{BT.failed}FAIL#{BT.reset} #{BT_STATE.error}/#{BT_STATE.count} tests failed" + false + end +end + +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 - if @tty and !@verbose - $stderr.printf("%-*s%s", @width, @basename, @progress[@count % @progress.size]) + + def add as + @all[as.path] << as + as.id = (@count += 1) end end -rescue Interrupt - $stderr.puts "\##{@count} #{@location}" - raise -rescue Exception => err - $stderr.print 'E' - $stderr.puts if @verbose - error err.message, message -ensure - begin - check_coredump - rescue CoreDumpError => err + + 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 + Assertion.errbuf << msg + end + 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 + $stderr.print '.' + end + else + $stderr.print "#{BT.failed}F" + $stderr.printf(" %.3f", t) if BT.verbose + $stderr.print BT.reset + $stderr.puts if BT.verbose + show_error faildesc, message + unless errout.empty? + $stderr.print "#{BT.failed}stderr output is not empty#{BT.reset}\n", adjust_indent(errout) + end + + if BT.tty and !BT.verbose and BT.wn == 1 + $stderr.printf("%-*s%s", BT.width, path, BT.progress[BT_STATE.count % BT.progress.size]) + end + end + rescue Interrupt + $stderr.puts "\##{@id} #{path}:#{lineno}" + raise + rescue Exception => err $stderr.print 'E' - $stderr.puts if @verbose - error err.message, message + $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 -end -def target_platform - if @ruby - `#{@ruby} --disable-gems -e 'print RUBY_PLATFORM'` - else - RUBY_PLATFORM + 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 -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 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) - 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 defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # for --jit-wait - timeout_seconds *= 3 - 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) @@ -461,66 +676,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"] - 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 @@ -548,21 +703,21 @@ def cleanup_coredump 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', @ruby, '-c', core_path, '-ex', 'bt', '-batch'] + 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_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_fiber.rb b/bootstraptest/test_fiber.rb index f651050b96..2614dd13bf 100644 --- a/bootstraptest/test_fiber.rb +++ b/bootstraptest/test_fiber.rb @@ -10,7 +10,7 @@ show_limit %q{ puts "Fiber count: #{fibers.count} (#{error})" break end while true -} unless @quiet +} assert_equal %q{ok}, %q{ Fiber.new{ diff --git a/bootstraptest/test_io.rb b/bootstraptest/test_io.rb index 89c00d0b88..c6c3772d36 100644 --- a/bootstraptest/test_io.rb +++ b/bootstraptest/test_io.rb @@ -30,7 +30,7 @@ assert_finish 10, %q{ end }, '[ruby-dev:32566]' -assert_finish 1, %q{ +assert_finish 5, %q{ r, w = IO.pipe Thread.new { w << "ab" @@ -83,6 +83,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_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_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 index d124d180d1..e80c50fd6e 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -1,32 +1,21 @@ -assert_equal '2022', %q{ - def contrivance(hash, key) - # Expect this to compile to an `opt_aref`. - hash[key] +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 - # The [] call above tracks that the `hash` local has a VALUE that - # is a heap pointer and the guard for the Kernel#itself call below - # doesn't check that it's a heap pointer VALUE. - # - # As you can see from the crash, the call to rb_hash_aref() can set the - # `hash` local, making eliding the heap object guard unsound. - hash.itself + # +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 - # This is similar to ->(recv, mid) { send(recv, mid).local_variable_set(...) }. - # By composing we avoid creating new Ruby frames and so sending :binding - # captures the environment of the frame that does the missing key lookup. - # We use it to capture the environment inside of `contrivance`. - cap_then_set = - Kernel.instance_method(:send).method(:bind_call).to_proc >> - ->(binding) { binding.local_variable_set(:hash, 2022) } - special_missing = Hash.new(&cap_then_set) - - # Make YJIT speculate that it's a hash and generate code - # that calls rb_hash_aref(). - contrivance({}, :warmup) - contrivance({}, :warmup) - - contrivance(special_missing, :binding) + local_setting_cmp(Object.new) + local_setting_cmp(Object.new) } assert_equal '18374962167983112447', %q{ @@ -45,7 +34,7 @@ assert_equal '18374962167983112447', %q{ } assert_normal_exit %q{ - # regression test for a leak caught by an asert on --yjit-call-threshold=2 + # regression test for a leak caught by an assert on --yjit-call-threshold=2 Foo = 1 eval("def foo = [#{(['Foo,']*256).join}]") @@ -1368,6 +1357,46 @@ assert_equal 'foo123', %q{ 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 @@ -1384,6 +1413,149 @@ assert_equal 'foo', %q{ 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 +} + +# Tes |